badline 0.10.1 → 0.11.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 +15 -0
- data/CLAUDE.md +5 -3
- data/README.md +59 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +182 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +120 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +320 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/computer/attachments.rb +4 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -0
- data/lib/badline/frontend/app.rb +34 -21
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_pages.rb +6 -1
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +15 -6
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +5 -1
- data/lib/badline/options/table.rb +7 -1
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +27 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- data/lib/badline.rb +1 -0
- metadata +24 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
class VDC
|
|
6
|
+
# Paints the characters of a displayed line, R1 of them from the
|
|
7
|
+
# line's first dot, in text mode or, with R25 bit 7, bitmap mode.
|
|
8
|
+
#
|
|
9
|
+
# Within a character the pixels come from R22: bits 4-7 hold its width
|
|
10
|
+
# less one (the width itself in double-width mode), and bits 0-3 the
|
|
11
|
+
# last pixel it displays (one more in double-width mode); a value past
|
|
12
|
+
# the width displays all of it. R25 bits 0-3 scroll the line left by
|
|
13
|
+
# the width less one less their value: the first character's pattern
|
|
14
|
+
# loads that many pixels before the display starts, and the displayed
|
|
15
|
+
# pixels run from the load to the R22 bits 0-3 count, as the
|
|
16
|
+
# Programmer's Reference Guide's table for R25 has it. Pixels past a
|
|
17
|
+
# pattern's eighth or the displayed count are the background, or with
|
|
18
|
+
# R25 bit 5, the semigraphic mode, the last displayed pixel repeated.
|
|
19
|
+
#
|
|
20
|
+
# Rows of characters are R9 + 1 lines, of which lines 0 to R23 display
|
|
21
|
+
# the pattern and the rest the background. R24 bits 0-4 scroll the
|
|
22
|
+
# frame up by that many lines. Row r reads its characters from
|
|
23
|
+
# R12/R13 + r * (R1 + R27) and their attributes from R20/R21 likewise,
|
|
24
|
+
# with both start addresses latched as each frame begins.
|
|
25
|
+
#
|
|
26
|
+
# In text mode a character's pattern is 16 bytes in its set, or 32
|
|
27
|
+
# with R9 above 15, from R28 bits 5-7 (bits 6-7 with 32). With
|
|
28
|
+
# attributes, R25 bit 6, bits 0-3 of the attribute are the colour, bit
|
|
29
|
+
# 4 blinks the character into the background, bit 5 underlines it on
|
|
30
|
+
# R29's line, bit 6 reverses it and bit 7 takes it from the second
|
|
31
|
+
# set. Without them the colour is R26 bits 4-7. The cursor, at R14/R15,
|
|
32
|
+
# reverses lines R10 bits 0-4 up to R11 less one, wrapping past the end
|
|
33
|
+
# of the character, and R10 bits 5-6 hold it solid, off, or blinking
|
|
34
|
+
# at 1/16 or 1/32 of the frame rate. Characters blink at 1/16 of the
|
|
35
|
+
# frame rate, or 1/32 with R24 bit 5. R24 bit 6 reverses everything.
|
|
36
|
+
#
|
|
37
|
+
# In bitmap mode scan line n of the frame reads its bytes from
|
|
38
|
+
# R12/R13 + n * (R1 + R27), and the attribute of each character cell
|
|
39
|
+
# holds its foreground in bits 0-3 and background in bits 4-7, or
|
|
40
|
+
# without attributes R26 holds them the other way round. The cursor
|
|
41
|
+
# isn't shown.
|
|
42
|
+
class Window
|
|
43
|
+
def initialize(registers, memory)
|
|
44
|
+
@registers = registers
|
|
45
|
+
@memory = memory
|
|
46
|
+
@display_start = 0
|
|
47
|
+
@attribute_start = 0
|
|
48
|
+
@layout = Array.new(16, 0)
|
|
49
|
+
@pattern = 0
|
|
50
|
+
@foreground = 0
|
|
51
|
+
@background = 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def latch_starts
|
|
55
|
+
regs = @registers
|
|
56
|
+
@display_start = (regs[12] << 8) | regs[13]
|
|
57
|
+
@attribute_start = (regs[20] << 8) | regs[21]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def save_state(out)
|
|
61
|
+
out.int(@display_start).int(@attribute_start)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_state(input)
|
|
65
|
+
@display_start = input.int
|
|
66
|
+
@attribute_start = input.int
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def paint(line, row, row_line, frame)
|
|
70
|
+
regs = @registers
|
|
71
|
+
lay_out_cell
|
|
72
|
+
height = (regs[9] & 0x1f) + 1
|
|
73
|
+
content = (row * height) + row_line + (regs[24] & 0x1f)
|
|
74
|
+
char_line = content % height
|
|
75
|
+
@gap_line = char_line > (regs[23] & 0x1f)
|
|
76
|
+
@stride = regs[1] + regs[27]
|
|
77
|
+
@attributes_on = regs[25].anybits?(0x40)
|
|
78
|
+
@screen_reverse = regs[24].anybits?(0x40)
|
|
79
|
+
if regs[25].anybits?(0x80)
|
|
80
|
+
paint_bitmap(line, content, content / height)
|
|
81
|
+
else
|
|
82
|
+
paint_text(line, content / height, char_line, frame)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# The pattern bit each pixel of a character shows, 0 for the first,
|
|
89
|
+
# or -1 for the background.
|
|
90
|
+
def lay_out_cell
|
|
91
|
+
regs = @registers
|
|
92
|
+
double = regs[25].anybits?(0x10)
|
|
93
|
+
@dot = double ? 2 : 1
|
|
94
|
+
@cell = double ? [regs[22] >> 4, 1].max : (regs[22] >> 4) + 1
|
|
95
|
+
@layout = Array.new(@cell, -1) if @layout.length < @cell
|
|
96
|
+
scroll = regs[25] & 0x0f
|
|
97
|
+
load = (scroll + 1) % @cell
|
|
98
|
+
@shift = (@cell - 1 - scroll) % @cell
|
|
99
|
+
last_shown = (regs[22] & 0x0f) - (double ? 1 : 0)
|
|
100
|
+
shown = last_shown >= @cell ? @cell : ((last_shown - load) % @cell) + 1
|
|
101
|
+
shown = [shown, 8].min
|
|
102
|
+
semigraphic = regs[25].anybits?(0x20)
|
|
103
|
+
@cell.times { |pixel| @layout[pixel] = cell_bit(pixel, shown, semigraphic) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def cell_bit(pixel, shown, semigraphic)
|
|
107
|
+
return pixel if pixel < shown
|
|
108
|
+
|
|
109
|
+
semigraphic ? shown - 1 : -1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def paint_text(line, char_row, char_line, frame)
|
|
113
|
+
regs = @registers
|
|
114
|
+
@screen = @display_start + (char_row * @stride)
|
|
115
|
+
@attributes = @attribute_start + (char_row * @stride)
|
|
116
|
+
tall = (regs[9] & 0x1f) > 15
|
|
117
|
+
@set = tall ? (regs[28] & 0xc0) << 8 : (regs[28] & 0xe0) << 8
|
|
118
|
+
@set_size = tall ? 0x2000 : 0x1000
|
|
119
|
+
@char_size = tall ? 32 : 16
|
|
120
|
+
@pattern_line = tall ? char_line : char_line & 0x0f
|
|
121
|
+
@underline = char_line == (regs[29] & 0x1f)
|
|
122
|
+
@blink_on = frame % (regs[24].anybits?(0x20) ? 32 : 16) < (regs[24].anybits?(0x20) ? 16 : 8)
|
|
123
|
+
@cursor = cursor_shown?(char_line, frame) ? (regs[14] << 8) | regs[15] : -1
|
|
124
|
+
each_cell(line) { |n| text_cell(n) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def paint_bitmap(line, content, char_row)
|
|
128
|
+
@bitmap = @display_start + (content * @stride)
|
|
129
|
+
@attributes = @attribute_start + (char_row * @stride)
|
|
130
|
+
each_cell(line) { |n| bitmap_cell(n) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def text_cell(index)
|
|
134
|
+
address = (@screen + index) & 0xffff
|
|
135
|
+
attribute = @attributes_on ? @memory.fetch((@attributes + index) & 0xffff) : 0
|
|
136
|
+
@pattern = text_pattern(@memory.fetch(address), attribute)
|
|
137
|
+
reverse = attribute.anybits?(0x40) ^ (address == @cursor) ^ @screen_reverse
|
|
138
|
+
foreground = @attributes_on ? attribute & 0x0f : @registers[26] >> 4
|
|
139
|
+
colours(foreground, @registers[26] & 0x0f, reverse)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def text_pattern(code, attribute)
|
|
143
|
+
return 0 if attribute.anybits?(0x10) && !@blink_on
|
|
144
|
+
return 0xff if attribute.anybits?(0x20) && @underline
|
|
145
|
+
return 0 if @gap_line
|
|
146
|
+
|
|
147
|
+
set = @set + (attribute.anybits?(0x80) ? @set_size : 0)
|
|
148
|
+
@memory.fetch((set + (code * @char_size) + @pattern_line) & 0xffff)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def bitmap_cell(index)
|
|
152
|
+
@pattern = @gap_line ? 0 : @memory.fetch((@bitmap + index) & 0xffff)
|
|
153
|
+
regs = @registers
|
|
154
|
+
if @attributes_on
|
|
155
|
+
attribute = @memory.fetch((@attributes + index) & 0xffff)
|
|
156
|
+
colours(attribute & 0x0f, attribute >> 4, @screen_reverse)
|
|
157
|
+
else
|
|
158
|
+
colours(regs[26] >> 4, regs[26] & 0x0f, @screen_reverse)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def colours(foreground, background, reverse)
|
|
163
|
+
@foreground = reverse ? background : foreground
|
|
164
|
+
@background = reverse ? foreground : background
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Paints the cells the display shows, one more when the line is
|
|
168
|
+
# scrolled, each after the block sets its pattern and colours.
|
|
169
|
+
def each_cell(line)
|
|
170
|
+
count = @registers[1] + (@shift.positive? ? 1 : 0)
|
|
171
|
+
right = @registers[1] * @cell
|
|
172
|
+
index = 0
|
|
173
|
+
while index < count
|
|
174
|
+
yield index
|
|
175
|
+
paint_cell(line, (index * @cell) - @shift, right)
|
|
176
|
+
index += 1
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def paint_cell(line, left, right)
|
|
181
|
+
gap = @registers[26] & 0x0f
|
|
182
|
+
pixel = 0
|
|
183
|
+
while pixel < @cell
|
|
184
|
+
x_pos = left + pixel
|
|
185
|
+
line.fill(pixel_colour(@layout[pixel], gap), x_pos * @dot, @dot) if x_pos >= 0 && x_pos < right
|
|
186
|
+
pixel += 1
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def pixel_colour(bit, gap)
|
|
191
|
+
return gap if bit.negative?
|
|
192
|
+
|
|
193
|
+
@pattern.anybits?(0x80 >> bit) ? @foreground : @background
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def cursor_shown?(char_line, frame)
|
|
197
|
+
mode = (@registers[10] >> 5) & 3
|
|
198
|
+
return false if mode == 1
|
|
199
|
+
return false if mode == 2 && (frame % 16) >= 8
|
|
200
|
+
return false if mode == 3 && (frame % 32) >= 16
|
|
201
|
+
|
|
202
|
+
cursor_line?(char_line)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def cursor_line?(char_line)
|
|
206
|
+
first = @registers[10] & 0x1f
|
|
207
|
+
stop = @registers[11] & 0x1f
|
|
208
|
+
return char_line >= first && char_line < stop if first < stop
|
|
209
|
+
|
|
210
|
+
first > stop && (char_line >= first || char_line < stop)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/c128/vdc/memory"
|
|
4
|
+
require "badline/c128/vdc/window"
|
|
5
|
+
require "badline/c128/vdc/painter"
|
|
6
|
+
require "badline/c128/vdc/raster"
|
|
7
|
+
|
|
8
|
+
module Badline
|
|
9
|
+
class C128
|
|
10
|
+
# The 8563 or 8568 VDC: the address register at $D600 and the data
|
|
11
|
+
# register at $D601, mirrored through $D6FF, in front of 37 internal
|
|
12
|
+
# registers (38 on the 8568), the VDC's own RAM (Memory) and its 80
|
|
13
|
+
# column RGBI display (Raster, Painter).
|
|
14
|
+
#
|
|
15
|
+
# A write to $D600 selects a register, and a read there gives the
|
|
16
|
+
# status: bit 7 ready for the next access, bit 5 in the vertical blank
|
|
17
|
+
# (outside the displayed rows), and bits 0-2 the version. $D601 reads
|
|
18
|
+
# and writes the selected register. A register past the last reads $FF.
|
|
19
|
+
# The light pen is left out.
|
|
20
|
+
#
|
|
21
|
+
# R18 and R19 hold the update address, high byte first, and a write to
|
|
22
|
+
# either reads the RAM there into R31. A read of R31 gives that byte,
|
|
23
|
+
# moves the address on and reads the next. A write of R31 stores the
|
|
24
|
+
# byte, moves the address on and reads the next. A write of R30 then
|
|
25
|
+
# repeats the last byte written over R30 more bytes (256 for 0), or with
|
|
26
|
+
# R24 bit 7 copies that many from R32/R33 on, and leaves the update
|
|
27
|
+
# address, and R32/R33 for a copy, after the last byte.
|
|
28
|
+
#
|
|
29
|
+
# The VDC runs on its own 16 MHz dot clock, a character clock every
|
|
30
|
+
# character width of dots (R22, doubled by R25 bit 4). The status reads
|
|
31
|
+
# not ready for ACCESS_CLOCKS character clocks after an access through
|
|
32
|
+
# R31 or a write of R18 or R19, and for one clock per byte a block write
|
|
33
|
+
# fills or two per byte it copies.
|
|
34
|
+
class VDC
|
|
35
|
+
include Addressable
|
|
36
|
+
|
|
37
|
+
STATUS_READY = 0x80
|
|
38
|
+
STATUS_VBLANK = 0x20
|
|
39
|
+
UPDATE_HIGH = 18
|
|
40
|
+
UPDATE_LOW = 19
|
|
41
|
+
WORD_COUNT = 30
|
|
42
|
+
DATA = 31
|
|
43
|
+
SOURCE_HIGH = 32
|
|
44
|
+
SOURCE_LOW = 33
|
|
45
|
+
DOT_CLOCK_HZ = 16_000_000
|
|
46
|
+
|
|
47
|
+
# Character clocks the status reads busy after an access through R31
|
|
48
|
+
# or a write of R18 or R19, fitted to the timings a flat C128 gave
|
|
49
|
+
# VDC/vdctiming (TimingResultsFlatC128.jpg).
|
|
50
|
+
ACCESS_CLOCKS = 30
|
|
51
|
+
|
|
52
|
+
# The version in the status bits: 1 for the 8563 R8 and R9, and 2 for
|
|
53
|
+
# the 8568.
|
|
54
|
+
VERSIONS = { mos8563: 1, mos8568: 2 }.freeze
|
|
55
|
+
|
|
56
|
+
# The 16 RGBI colours, red, green, blue and intensity from bit 3 down,
|
|
57
|
+
# as the Programmer's Reference Guide names them: 1100 is dark yellow.
|
|
58
|
+
PALETTE = Array.new(16) do |rgbi|
|
|
59
|
+
intensity = rgbi.anybits?(1) ? 0x55 : 0
|
|
60
|
+
red = (rgbi.anybits?(8) ? 0xaa : 0) + intensity
|
|
61
|
+
green = (rgbi.anybits?(4) ? 0xaa : 0) + intensity
|
|
62
|
+
blue = (rgbi.anybits?(2) ? 0xaa : 0) + intensity
|
|
63
|
+
(red << 16) | (green << 8) | blue
|
|
64
|
+
end.freeze
|
|
65
|
+
|
|
66
|
+
attr_reader :model, :registers
|
|
67
|
+
|
|
68
|
+
# Whether lines paint into the display as the raster passes them.
|
|
69
|
+
attr_reader :render
|
|
70
|
+
|
|
71
|
+
# +ram_kb+ is 16 or 64, and +clock_hz+ the CPU's clock the VDC is
|
|
72
|
+
# clocked against.
|
|
73
|
+
def initialize(model: :mos8563, ram_kb: 16, clock_hz: Region::PAL.clock_hz)
|
|
74
|
+
addressable_at(0xd600, length: 0x100)
|
|
75
|
+
@model = model
|
|
76
|
+
@version = VERSIONS.fetch(model)
|
|
77
|
+
@clock_hz = clock_hz
|
|
78
|
+
@registers = Array.new(model == :mos8568 ? 38 : 37, 0)
|
|
79
|
+
@memory = Memory.new(ram_kb)
|
|
80
|
+
@painter = Painter.new(@registers, @memory)
|
|
81
|
+
@raster = Raster.new(@registers, @painter)
|
|
82
|
+
@render = false
|
|
83
|
+
power_on!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The dots a character takes, R22 bits 4-7 plus one, or R22 bits 4-7
|
|
87
|
+
# doubled in double-width mode.
|
|
88
|
+
def self.char_dots(registers)
|
|
89
|
+
width = registers[22] >> 4
|
|
90
|
+
registers[25].anybits?(0x10) ? [width, 1].max * 2 : width + 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The registers clear, the RAM clears and the raster starts a frame.
|
|
94
|
+
def power_on!
|
|
95
|
+
@registers.fill(0)
|
|
96
|
+
@memory.clear!
|
|
97
|
+
@memory.mode64 = false
|
|
98
|
+
@selected = 0
|
|
99
|
+
@read_latch = 0
|
|
100
|
+
@write_data = 0
|
|
101
|
+
@busy_until = 0
|
|
102
|
+
@phase = 0
|
|
103
|
+
@raster.reset!
|
|
104
|
+
@line_units = @raster.line_dots * @clock_hz
|
|
105
|
+
@painter.clear!
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ram = @memory.ram
|
|
109
|
+
|
|
110
|
+
def render=(on)
|
|
111
|
+
@render = on
|
|
112
|
+
@painter.clear!
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Everything the VDC holds but its display, which it paints again
|
|
116
|
+
# while it renders: the registers, the RAM, the data port and the
|
|
117
|
+
# raster. Whether it renders is the host's.
|
|
118
|
+
def save_state(out)
|
|
119
|
+
out.marker("VDC")
|
|
120
|
+
out.ints(@registers).blob(@memory.ram)
|
|
121
|
+
out.int(@selected).int(@read_latch).int(@write_data).int(@busy_until).int(@phase).int(@line_units)
|
|
122
|
+
@raster.save_state(out)
|
|
123
|
+
@painter.save_state(out)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def load_state(input)
|
|
127
|
+
input.marker("VDC")
|
|
128
|
+
input.ints_into(@registers)
|
|
129
|
+
input.blob_into(@memory.ram)
|
|
130
|
+
@memory.mode64 = @registers[28].anybits?(0x10)
|
|
131
|
+
@selected = input.int
|
|
132
|
+
@read_latch = input.int
|
|
133
|
+
@write_data = input.int
|
|
134
|
+
@busy_until = input.int
|
|
135
|
+
@phase = input.int
|
|
136
|
+
@line_units = input.int
|
|
137
|
+
@raster.load_state(input)
|
|
138
|
+
@painter.load_state(input)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The frames the raster has finished, which the blinking counts.
|
|
142
|
+
def frame = @raster.frame
|
|
143
|
+
|
|
144
|
+
def display = @painter.display
|
|
145
|
+
|
|
146
|
+
def width = @painter.width
|
|
147
|
+
|
|
148
|
+
def height = @painter.height
|
|
149
|
+
|
|
150
|
+
def dirty_lines = @painter.dirty_lines
|
|
151
|
+
|
|
152
|
+
def clear_dirty_lines! = @painter.clear_dirty_lines!
|
|
153
|
+
|
|
154
|
+
def palette = PALETTE
|
|
155
|
+
|
|
156
|
+
# The [left, top, width, height] of the display a monitor shows.
|
|
157
|
+
def crop = @painter.crop(@raster.line_dots, @raster.frame_lines, @raster.sync_lines)
|
|
158
|
+
|
|
159
|
+
# The update address R18 and R19 hold.
|
|
160
|
+
def update_address = (@registers[UPDATE_HIGH] << 8) | @registers[UPDATE_LOW]
|
|
161
|
+
|
|
162
|
+
# Whether an access or a block operation is still under way.
|
|
163
|
+
def busy? = @phase < @busy_until
|
|
164
|
+
|
|
165
|
+
# One CPU cycle: the dot clock runs on, and a line ends once it has
|
|
166
|
+
# run a line's dots. The line's phase counts dots times the CPU's
|
|
167
|
+
# clock, DOT_CLOCK_HZ a cycle.
|
|
168
|
+
def cycle!
|
|
169
|
+
@phase += DOT_CLOCK_HZ
|
|
170
|
+
end_line if @phase >= @line_units
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def peek(addr)
|
|
174
|
+
return status if offset_of(addr).even?
|
|
175
|
+
return read_ram if @selected == DATA
|
|
176
|
+
|
|
177
|
+
@selected < @registers.length ? @registers[@selected] : 0xff
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def poke(addr, value)
|
|
181
|
+
return @selected = value & 0x3f if offset_of(addr).even?
|
|
182
|
+
return if @selected >= @registers.length
|
|
183
|
+
|
|
184
|
+
write_register(@selected, value)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def status
|
|
190
|
+
value = @version
|
|
191
|
+
value |= STATUS_READY unless busy?
|
|
192
|
+
value |= STATUS_VBLANK if @raster.vertical_blank?
|
|
193
|
+
value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def end_line
|
|
197
|
+
@phase -= @line_units
|
|
198
|
+
@busy_until -= @line_units
|
|
199
|
+
@raster.end_line(@render)
|
|
200
|
+
@line_units = @raster.line_dots * @clock_hz
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def write_register(number, value)
|
|
204
|
+
@registers[number] = value
|
|
205
|
+
case number
|
|
206
|
+
when DATA then write_ram(value)
|
|
207
|
+
when WORD_COUNT then block(value.zero? ? 256 : value)
|
|
208
|
+
when UPDATE_HIGH, UPDATE_LOW then read_ahead
|
|
209
|
+
when 28 then @memory.mode64 = value.anybits?(0x10)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def read_ram
|
|
214
|
+
value = @read_latch
|
|
215
|
+
advance(1)
|
|
216
|
+
read_ahead
|
|
217
|
+
value
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def write_ram(value)
|
|
221
|
+
@memory.store(update_address, value)
|
|
222
|
+
@write_data = value
|
|
223
|
+
advance(1)
|
|
224
|
+
read_ahead
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def read_ahead
|
|
228
|
+
@read_latch = @memory.fetch(update_address)
|
|
229
|
+
busy(ACCESS_CLOCKS)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def block(count)
|
|
233
|
+
copy = @registers[24].anybits?(0x80)
|
|
234
|
+
copy ? copy_block(count) : fill_block(count)
|
|
235
|
+
@read_latch = @memory.fetch(update_address)
|
|
236
|
+
busy(copy ? count * 2 : count)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def fill_block(count)
|
|
240
|
+
address = update_address
|
|
241
|
+
count.times { |i| @memory.store(address + i, @write_data) }
|
|
242
|
+
advance(count)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def copy_block(count)
|
|
246
|
+
destination = update_address
|
|
247
|
+
source = (@registers[SOURCE_HIGH] << 8) | @registers[SOURCE_LOW]
|
|
248
|
+
count.times { |i| @memory.store(destination + i, @memory.fetch(source + i)) }
|
|
249
|
+
advance(count)
|
|
250
|
+
source = (source + count) & 0xffff
|
|
251
|
+
@registers[SOURCE_HIGH] = source >> 8
|
|
252
|
+
@registers[SOURCE_LOW] = source & 0xff
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def advance(count)
|
|
256
|
+
address = (update_address + count) & 0xffff
|
|
257
|
+
@registers[UPDATE_HIGH] = address >> 8
|
|
258
|
+
@registers[UPDATE_LOW] = address & 0xff
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Holds the status busy for `clocks` character clocks after whatever
|
|
262
|
+
# is already under way.
|
|
263
|
+
def busy(clocks)
|
|
264
|
+
@busy_until = [@busy_until, @phase].max + (clocks * VDC.char_dots(@registers) * @clock_hz)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|