badline 0.9.0 → 0.10.1
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 +48 -0
- data/CLAUDE.md +6 -4
- data/README.md +39 -4
- data/doc/library.md +15 -1
- data/doc/pinned-behaviour.md +146 -0
- data/doc/snapshots.md +17 -3
- data/lib/badline/address_bus/fittings.rb +38 -0
- data/lib/badline/address_bus/pla.rb +160 -0
- data/lib/badline/address_bus/roms.rb +12 -5
- data/lib/badline/address_bus.rb +15 -90
- 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 +52 -37
- 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 +28 -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/model.rb +29 -12
- data/lib/badline/options/event.rb +5 -3
- data/lib/badline/options/help.rb +11 -3
- data/lib/badline/options/table.rb +12 -2
- data/lib/badline/options/validation.rb +55 -4
- data/lib/badline/options.rb +33 -33
- data/lib/badline/roms/README +51 -0
- data/lib/badline/roms/c64gs.rom +0 -0
- data/lib/badline/roms/kernal-pet64.rom +0 -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/setup.rb +15 -10
- data/lib/badline/snapshot/state.rb +5 -3
- data/lib/badline/snapshot/vice.rb +2 -1
- 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 +55 -17
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic.rb +48 -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 +31 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/vic20/vic/painter"
|
|
4
|
+
require "badline/vic20/vic/text_window"
|
|
5
|
+
require "badline/vic20/vic/saved_state"
|
|
6
|
+
|
|
7
|
+
module Badline
|
|
8
|
+
class Vic20
|
|
9
|
+
# The VIC-I, the 6561 on PAL machines: its sixteen registers, the
|
|
10
|
+
# raster, the text window's fetches over the V-bus and the picture. The
|
|
11
|
+
# writes to the sound's registers, $900A-$900E, go on to the Sound
|
|
12
|
+
# attached as #sound.
|
|
13
|
+
#
|
|
14
|
+
# The registers repeat through $9000-$90FF. $9003 bit 7 and $9004 read
|
|
15
|
+
# the raster line, its bit 0 and bits 8-1. $9006 and $9007 read the
|
|
16
|
+
# light pen, which nothing triggers yet, and $9008 and $9009 the two
|
|
17
|
+
# pots, which read $FF with nothing plugged in. The other registers
|
|
18
|
+
# read back what was written to them.
|
|
19
|
+
#
|
|
20
|
+
# The text window opens on the line where the raster's bits 8-1 match
|
|
21
|
+
# $9001, and closes after the number of text rows in $9003, each 8 lines
|
|
22
|
+
# of a character, or 16 with $9003 bit 0 set. On each of its lines the
|
|
23
|
+
# VIC fetches two bytes a character, $9002's count of them: the
|
|
24
|
+
# character code from the video matrix, with the colour nibble beside it
|
|
25
|
+
# on the colour bus, then the character's pattern byte for the line.
|
|
26
|
+
# The first code is fetched four cycles after the column in $9000, so
|
|
27
|
+
# the window starts 16 cycles into the line with the KERNAL's origin of
|
|
28
|
+
# 12, and none is fetched past the end of the line. The fetches go to a
|
|
29
|
+
# 14-bit address, the VIC's own (see Bus#video_fetch), and the byte each
|
|
30
|
+
# one reads is the V-bus's last byte. Outside the window the 6561E
|
|
31
|
+
# fetches nothing and the V-bus keeps the CPU's last byte there.
|
|
32
|
+
#
|
|
33
|
+
# The video matrix counter steps once a character. It starts each line
|
|
34
|
+
# of a text row where the row started, and the next row starts where
|
|
35
|
+
# the last line of the row left it. It starts the frame at zero.
|
|
36
|
+
#
|
|
37
|
+
# The picture is 4 pixels a cycle, so 284 a line. A character's 8
|
|
38
|
+
# pixels come out in the two cycles after its pattern fetch. The
|
|
39
|
+
# display's lines start 6 cycles after the raster count moves, as
|
|
40
|
+
# xvic's view of the frame does, so the character fetched 16 cycles
|
|
41
|
+
# into the line lands 48 pixels in. Lines 0-27 are blanked.
|
|
42
|
+
class VIC
|
|
43
|
+
include TextWindow
|
|
44
|
+
include SavedState
|
|
45
|
+
|
|
46
|
+
# What a VIC-I sets for the machine: the clock, the raster's cycles
|
|
47
|
+
# per line and lines per frame, and how many lines at the top of the
|
|
48
|
+
# frame it blanks. The 6560 of NTSC machines, with its half lines and
|
|
49
|
+
# the interlace of $9000 bit 7, would be another.
|
|
50
|
+
Profile = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :blanked_lines)
|
|
51
|
+
|
|
52
|
+
# The 6561: 71 cycles by 312 lines, lines 0-27 blanked (Marko
|
|
53
|
+
# Mäkelä's VIC-I.txt).
|
|
54
|
+
PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28)
|
|
55
|
+
|
|
56
|
+
PIXELS_PER_CYCLE = 4
|
|
57
|
+
|
|
58
|
+
# The column whose pixels start a line of the display.
|
|
59
|
+
FIRST_PIXEL_COLUMN = 6
|
|
60
|
+
|
|
61
|
+
# How many cycles after the column in $9000 the first code is fetched.
|
|
62
|
+
FETCH_DELAY = 4
|
|
63
|
+
|
|
64
|
+
# The colours, as RGB, each from its luma and chroma: luma in five
|
|
65
|
+
# levels from black to white, and chroma at one strength in a phase
|
|
66
|
+
# that steps by 22.5 degrees from blue's. The levels and phases are
|
|
67
|
+
# the VIC-I's table on Wikipedia's "MOS Technology VIC" (Y, Pb, Pr),
|
|
68
|
+
# turned into RGB with the BT.601 matrix and the chroma scaled by 0.2.
|
|
69
|
+
PALETTE = [
|
|
70
|
+
0x000000, 0xffffff, 0x82251d, 0x7ddae2, 0xb259bf, 0x4da640, 0x402e9a, 0xbfd165,
|
|
71
|
+
0xb27240, 0xf2b27f, 0xc2655d, 0xbdffff, 0xf299ff, 0x8de57f, 0x806eda, 0xffffa5
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
attr_reader :rasterline, :column, :profile
|
|
75
|
+
attr_accessor :sound
|
|
76
|
+
|
|
77
|
+
def initialize(profile = PAL)
|
|
78
|
+
@profile = profile
|
|
79
|
+
@cycles_per_line = profile.cycles_per_line
|
|
80
|
+
@lines_per_frame = profile.lines_per_frame
|
|
81
|
+
@painter = Painter.new(@lines_per_frame, @cycles_per_line * PIXELS_PER_CYCLE, profile.blanked_lines)
|
|
82
|
+
@patterns = @painter.patterns
|
|
83
|
+
@colors = @painter.colors
|
|
84
|
+
@counts = @painter.counts
|
|
85
|
+
@starts = @painter.starts
|
|
86
|
+
@stale = @painter.stale
|
|
87
|
+
@bus = nil
|
|
88
|
+
@sound = nil
|
|
89
|
+
@color_cells = Array.new(0x400, 0)
|
|
90
|
+
power_on!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The bus the VIC fetches from, the machine's, and the colour RAM
|
|
94
|
+
# whose cells come in on the colour bus beside each code.
|
|
95
|
+
def connect(bus)
|
|
96
|
+
@bus = bus
|
|
97
|
+
@color_cells = bus.color_ram.cells
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The power-on state: the registers cleared and the raster at the top
|
|
101
|
+
# of the frame. The VIC has no RES pin, so only power reaches it.
|
|
102
|
+
def power_on!
|
|
103
|
+
@registers = Array.new(16, 0)
|
|
104
|
+
@rasterline = 0
|
|
105
|
+
@column = 0
|
|
106
|
+
@origin_x = @origin_y = @columns = @rows = 0
|
|
107
|
+
@char_shift = 3
|
|
108
|
+
@screen_base = @char_base = 0
|
|
109
|
+
new_frame
|
|
110
|
+
@fetch_from = @fetch_to = 0
|
|
111
|
+
@fetched = @slot = 0
|
|
112
|
+
@code = 0
|
|
113
|
+
@painter.power_on!(@lines_per_frame - 1)
|
|
114
|
+
schedule
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def cycle!
|
|
118
|
+
column = @column + 1
|
|
119
|
+
@column = column
|
|
120
|
+
return unless column == @next_event
|
|
121
|
+
|
|
122
|
+
if column > FIRST_PIXEL_COLUMN && column < @fetch_to && column >= @fetch_from
|
|
123
|
+
access(column)
|
|
124
|
+
column += 1
|
|
125
|
+
@next_event = column < @fetch_to ? column : @cycles_per_line
|
|
126
|
+
else
|
|
127
|
+
event(column)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The sixteen registers as written.
|
|
132
|
+
def register_file = @registers.dup
|
|
133
|
+
|
|
134
|
+
# The display, a palette index a pixel, 284 to a line and every line
|
|
135
|
+
# of the frame.
|
|
136
|
+
def display = @painter.display
|
|
137
|
+
|
|
138
|
+
def width = @painter.width
|
|
139
|
+
|
|
140
|
+
def height = @lines_per_frame
|
|
141
|
+
|
|
142
|
+
# Whether each line of the display has changed since the front end
|
|
143
|
+
# last cleared them.
|
|
144
|
+
def dirty_lines = @painter.dirty_lines
|
|
145
|
+
|
|
146
|
+
def clear_dirty_lines! = @painter.clear_dirty_lines!
|
|
147
|
+
|
|
148
|
+
def palette = PALETTE
|
|
149
|
+
|
|
150
|
+
# Whether the VIC paints its display. It fetches either way.
|
|
151
|
+
def render = @painter.render
|
|
152
|
+
|
|
153
|
+
def render=(value)
|
|
154
|
+
@painter.render = value
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def peek(addr)
|
|
158
|
+
register = addr & 0x0f
|
|
159
|
+
case register
|
|
160
|
+
when 0x03 then ((@rasterline & 0x01) << 7) | (@registers[3] & 0x7f)
|
|
161
|
+
when 0x04 then @rasterline >> 1
|
|
162
|
+
when 0x06, 0x07 then 0
|
|
163
|
+
when 0x08, 0x09 then 0xff
|
|
164
|
+
else @registers[register]
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def poke(addr, value)
|
|
169
|
+
register = addr & 0x0f
|
|
170
|
+
@registers[register] = value
|
|
171
|
+
case register
|
|
172
|
+
when 0x00, 0x02 then window_written
|
|
173
|
+
when 0x01 then @origin_y = value
|
|
174
|
+
when 0x03 then rows_written(value)
|
|
175
|
+
when 0x05 then bases_written
|
|
176
|
+
when 0x0a, 0x0b, 0x0c, 0x0d then @sound&.write(register, value)
|
|
177
|
+
when 0x0e
|
|
178
|
+
@painter.aux_written(pixel_position, value >> 4)
|
|
179
|
+
@sound&.write(register, value)
|
|
180
|
+
when 0x0f then @painter.colors_written(pixel_position, value)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def event(column)
|
|
187
|
+
if column == @cycles_per_line
|
|
188
|
+
column = @column = 0
|
|
189
|
+
start_line
|
|
190
|
+
end
|
|
191
|
+
@painter.finish_line(@rasterline) if column == FIRST_PIXEL_COLUMN
|
|
192
|
+
access(column) if column >= @fetch_from && column < @fetch_to
|
|
193
|
+
schedule
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The next column with something to do: the end of the line, the
|
|
197
|
+
# start of a display line, or a fetch.
|
|
198
|
+
def schedule
|
|
199
|
+
column = @column
|
|
200
|
+
upcoming = column < FIRST_PIXEL_COLUMN ? FIRST_PIXEL_COLUMN : @cycles_per_line
|
|
201
|
+
fetch = column < @fetch_from ? @fetch_from : column + 1
|
|
202
|
+
upcoming = fetch if fetch < @fetch_to && fetch < upcoming
|
|
203
|
+
@next_event = upcoming
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def start_line
|
|
207
|
+
line = @rasterline + 1
|
|
208
|
+
if line == @lines_per_frame
|
|
209
|
+
line = 0
|
|
210
|
+
new_frame
|
|
211
|
+
end
|
|
212
|
+
@rasterline = line
|
|
213
|
+
next_text_line if @window_open
|
|
214
|
+
open_window if !@window_open && !@window_done && (line >> 1) == @origin_y && @rows.positive?
|
|
215
|
+
place_fetches
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def place_fetches
|
|
219
|
+
line = @rasterline
|
|
220
|
+
@slot = line * Painter::SLOTS
|
|
221
|
+
@fetched = @counts[line] = 0
|
|
222
|
+
@starts[line] = @origin_x * PIXELS_PER_CYCLE
|
|
223
|
+
unless @window_open
|
|
224
|
+
@fetch_from = @fetch_to = 0
|
|
225
|
+
return
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
@fetch_from = @origin_x + FETCH_DELAY
|
|
229
|
+
fetches_end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def fetches_end
|
|
233
|
+
last = @fetch_from + (2 * @columns)
|
|
234
|
+
@fetch_to = [last, @cycles_per_line].min
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def access(column)
|
|
238
|
+
slot = @slot + @fetched
|
|
239
|
+
if (column - @fetch_from).even?
|
|
240
|
+
address = (@screen_base + @matrix) & 0x3fff
|
|
241
|
+
@code = @bus.video_fetch(address)
|
|
242
|
+
color = @color_cells[address & 0x3ff]
|
|
243
|
+
return if @colors[slot] == color
|
|
244
|
+
|
|
245
|
+
@colors[slot] = color
|
|
246
|
+
else
|
|
247
|
+
pattern = @bus.video_fetch((@char_base + (@code << @char_shift) + @char_line) & 0x3fff)
|
|
248
|
+
@matrix += 1
|
|
249
|
+
@fetched += 1
|
|
250
|
+
@counts[@rasterline] = @fetched
|
|
251
|
+
return if @patterns[slot] == pattern
|
|
252
|
+
|
|
253
|
+
@patterns[slot] = pattern
|
|
254
|
+
end
|
|
255
|
+
@stale[@rasterline] = true
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The pixel of the display line a register write in this cycle takes
|
|
259
|
+
# hold from: the second of the cycle's four.
|
|
260
|
+
def pixel_position
|
|
261
|
+
column = @column - FIRST_PIXEL_COLUMN
|
|
262
|
+
column += @cycles_per_line if column.negative?
|
|
263
|
+
(column * PIXELS_PER_CYCLE) + 1
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/vic20/attachments"
|
|
4
|
+
require "badline/vic20/bus"
|
|
5
|
+
require "badline/vic20/cpu"
|
|
6
|
+
require "badline/vic20/keyboard_via_ports"
|
|
7
|
+
require "badline/vic20/port_wiring"
|
|
8
|
+
require "badline/vic20/user_via_ports"
|
|
9
|
+
require "badline/vic20/vic"
|
|
10
|
+
require "badline/vic20/sound"
|
|
11
|
+
require "badline/vic20/saved_state"
|
|
12
|
+
|
|
13
|
+
module Badline
|
|
14
|
+
# The Commodore VIC-20 with the PAL 6561: a 6502 at the VIC's clock, the
|
|
15
|
+
# VIC-I and two 6522 VIAs. VIA 1, at $9110, pulls NMI, and VIA 2, at
|
|
16
|
+
# $9120, pulls IRQ. Nothing else interrupts the CPU, and nothing halts
|
|
17
|
+
# it: the VIC fetches in the half of the cycle the CPU leaves alone.
|
|
18
|
+
#
|
|
19
|
+
# It runs in the window, as `badline vic20`, with the VIC's picture and
|
|
20
|
+
# sound. The keyboard, the joystick and RESTORE reach the VIAs, the
|
|
21
|
+
# datasette and the serial bus hang off them (PortWiring), and device 8
|
|
22
|
+
# serves disks through the KERNAL traps or a true 1541.
|
|
23
|
+
class Vic20
|
|
24
|
+
include IntegerHelper
|
|
25
|
+
include KeyboardBuffer
|
|
26
|
+
include Attachments
|
|
27
|
+
include PortWiring
|
|
28
|
+
include SavedState
|
|
29
|
+
|
|
30
|
+
attr_reader :bus, :cpu, :vic, :via1, :via2, :keyboard, :joystick1, :cycles, :init_threshold, :sound,
|
|
31
|
+
:drive1541, :iec_bus, :datasette
|
|
32
|
+
|
|
33
|
+
# The RAM expansion it was built with, a key of Bus::RAM_CONFIGURATIONS.
|
|
34
|
+
attr_reader :ram_configuration
|
|
35
|
+
|
|
36
|
+
# The path of the disk or directory device 8 serves through the traps
|
|
37
|
+
# (Attachments), or an empty one.
|
|
38
|
+
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
39
|
+
|
|
40
|
+
def ram = @bus.ram
|
|
41
|
+
|
|
42
|
+
def family = :vic20
|
|
43
|
+
|
|
44
|
+
# The one control port's joystick, which the front end's second
|
|
45
|
+
# joystick drives too.
|
|
46
|
+
def joystick2 = @joystick1
|
|
47
|
+
|
|
48
|
+
# No pot device plugs in yet.
|
|
49
|
+
def control_ports = nil
|
|
50
|
+
|
|
51
|
+
# +ram+ names the RAM expansion, one of Bus::RAM_CONFIGURATIONS.
|
|
52
|
+
def initialize(ram: :unexpanded, debug: false)
|
|
53
|
+
@ram_configuration = ram
|
|
54
|
+
@vic = VIC.new
|
|
55
|
+
@keyboard = Keyboard.new(matrix: KeyboardVIAPorts::MATRIX)
|
|
56
|
+
@joystick1 = Joystick.new
|
|
57
|
+
@iec_bus = IECBus.new
|
|
58
|
+
@datasette = Datasette.new
|
|
59
|
+
@via1 = VIA.new(start: 0x9000, peripheral: UserVIAPorts.new(joystick: @joystick1, serial_bus: @iec_bus,
|
|
60
|
+
datasette: @datasette))
|
|
61
|
+
keyboard_ports = KeyboardVIAPorts.new(keyboard: @keyboard, joystick: @joystick1)
|
|
62
|
+
@via2 = VIA.new(start: 0x9000, peripheral: keyboard_ports)
|
|
63
|
+
keyboard_ports.connect(@via2)
|
|
64
|
+
@bus = Bus.new(vic: @vic, via1: @via1, via2: @via2, blocks: Bus::RAM_CONFIGURATIONS.fetch(ram))
|
|
65
|
+
@vic.connect(@bus)
|
|
66
|
+
@cpu = CPU.new(@bus, debug:)
|
|
67
|
+
@cycles = 0
|
|
68
|
+
@sound = Sound.new(self, @vic.profile.clock_hz)
|
|
69
|
+
@vic.sound = @sound
|
|
70
|
+
@nmi_asserted = false
|
|
71
|
+
@init_handlers = []
|
|
72
|
+
@pending_keys = nil
|
|
73
|
+
@drive = nil
|
|
74
|
+
@serial_trap = nil
|
|
75
|
+
@save_trap = nil
|
|
76
|
+
@capture_output = nil
|
|
77
|
+
@drive1541 = nil
|
|
78
|
+
@init_threshold = boot_cycles
|
|
79
|
+
wire_ports
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The VIC and the VIAs clock ahead of the CPU, as the C64's chips do,
|
|
83
|
+
# so a register write lands on the cycle after the one it was issued
|
|
84
|
+
# on.
|
|
85
|
+
def cycle!
|
|
86
|
+
handle_init if @cycles == @init_threshold
|
|
87
|
+
feed_keyboard if @pending_keys
|
|
88
|
+
|
|
89
|
+
@vic.cycle!
|
|
90
|
+
@via1.cycle!
|
|
91
|
+
@via2.cycle!
|
|
92
|
+
@datasette.cycle!
|
|
93
|
+
@cpu.irq = @via2.irq?
|
|
94
|
+
drive_nmi
|
|
95
|
+
@cpu.cycle!
|
|
96
|
+
@drive1541&.host_cycle!
|
|
97
|
+
|
|
98
|
+
@cycles += 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Runs `count` cycles, one #cycle! after another.
|
|
102
|
+
def run_cycles(count)
|
|
103
|
+
i = 0
|
|
104
|
+
while i < count
|
|
105
|
+
cycle!
|
|
106
|
+
i += 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Runs cycles until the block returns true or the cycle count passes
|
|
111
|
+
# `limit`, checking before each cycle.
|
|
112
|
+
def run_until(limit)
|
|
113
|
+
cycle! until yield || @cycles > limit
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Runs the block once the KERNAL has booted (see init_threshold), or
|
|
117
|
+
# now if it has.
|
|
118
|
+
def on_init(&block)
|
|
119
|
+
if @cycles < @init_threshold
|
|
120
|
+
@init_handlers << block
|
|
121
|
+
else
|
|
122
|
+
block.call
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The chip whose #display a front end shows.
|
|
127
|
+
def video = @vic
|
|
128
|
+
|
|
129
|
+
# What a front end plays: the VIC's sound, which answers #record and
|
|
130
|
+
# #drain_samples.
|
|
131
|
+
def sound_source = @sound
|
|
132
|
+
|
|
133
|
+
# The PAL clock and raster, and the part of the display xvic shows: all
|
|
134
|
+
# 284 pixels of a line, from line 28, below the VIC's vertical blank.
|
|
135
|
+
# The VIC draws 4 pixels a cycle, half as many as the C64's VIC-II on
|
|
136
|
+
# about the same clock, so each pixel shows two window pixels wide, as
|
|
137
|
+
# xvic draws them.
|
|
138
|
+
def timing
|
|
139
|
+
profile = @vic.profile
|
|
140
|
+
width = profile.cycles_per_line * VIC::PIXELS_PER_CYCLE
|
|
141
|
+
blanked = profile.blanked_lines
|
|
142
|
+
Timing.new(clock_hz: profile.clock_hz, cycles_per_line: profile.cycles_per_line,
|
|
143
|
+
lines_per_frame: profile.lines_per_frame,
|
|
144
|
+
crop: [0, blanked, width, profile.lines_per_frame - blanked], pixel_width: 2)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# The RESTORE key pulls VIA 1's CA1 low while it is held. The KERNAL
|
|
148
|
+
# sets CA1 to interrupt on that falling edge, and its NMI handler
|
|
149
|
+
# ($FEAD) warm-starts BASIC if RUN/STOP is down too.
|
|
150
|
+
def press_restore
|
|
151
|
+
@via1.ca1 = false
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def release_restore
|
|
155
|
+
@via1.ca1 = true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Calls the block with the exit code a VICE testprog writes to $910F.
|
|
159
|
+
def install_debug_register(&) = @bus.install_debug_register(&)
|
|
160
|
+
|
|
161
|
+
# Stores a PRG's bytes from its load address on, as the CPU would, so
|
|
162
|
+
# bytes for an empty block go nowhere, and returns that address.
|
|
163
|
+
def load_prg(data)
|
|
164
|
+
load_addr = uint16(data[0], data[1])
|
|
165
|
+
bytes = data[2..] || []
|
|
166
|
+
bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
|
|
167
|
+
bytes.each_with_index { |byte, i| @bus.poke(load_addr + i, byte) }
|
|
168
|
+
load_addr
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The RES line reaches the CPU and both VIAs, and through the serial
|
|
172
|
+
# bus's RESET line, the drive. The VIC has no reset pin.
|
|
173
|
+
def reset!
|
|
174
|
+
@via1.reset!
|
|
175
|
+
@via2.reset!
|
|
176
|
+
via_written
|
|
177
|
+
@drive&.reset!
|
|
178
|
+
@drive1541&.reset!
|
|
179
|
+
@nmi_asserted = false
|
|
180
|
+
@cpu.reset!
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Switches the machine off and on: RAM and the VIC start from their
|
|
184
|
+
# power-on state, and the RES line resets everything else.
|
|
185
|
+
def power_cycle!
|
|
186
|
+
@bus.power_on!
|
|
187
|
+
@vic.power_on!
|
|
188
|
+
@sound.power_on!
|
|
189
|
+
reset!
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def inspect
|
|
193
|
+
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
# The cycle by which the KERNAL has booted and BASIC printed READY,
|
|
199
|
+
# with a margin. The KERNAL's RAM test takes most of it, and runs up
|
|
200
|
+
# from $1000 until it finds no RAM, so each 8K block on from BLK1
|
|
201
|
+
# adds about 615,000 cycles. Unexpanded, READY is up by about 557,000.
|
|
202
|
+
def boot_cycles
|
|
203
|
+
tested = 0
|
|
204
|
+
tested += 1 while tested < 3 && @bus.ram?(%i[blk1 blk2 blk3][tested])
|
|
205
|
+
700_000 + (650_000 * tested)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# VIA 1's IRQ output drives NMI, and the CPU takes an interrupt on its
|
|
209
|
+
# falling edge.
|
|
210
|
+
def drive_nmi
|
|
211
|
+
nmi = @via1.irq?
|
|
212
|
+
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
213
|
+
@nmi_asserted = nmi
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def handle_init
|
|
217
|
+
@init_handlers.each(&:call)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: badline
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Inge Jørgensen
|
|
@@ -50,10 +50,11 @@ files:
|
|
|
50
50
|
- lib/badline.rb
|
|
51
51
|
- lib/badline/address_bus.rb
|
|
52
52
|
- lib/badline/address_bus/extra_sids.rb
|
|
53
|
+
- lib/badline/address_bus/fittings.rb
|
|
54
|
+
- lib/badline/address_bus/pla.rb
|
|
53
55
|
- lib/badline/address_bus/roms.rb
|
|
54
56
|
- lib/badline/address_bus/saved_state.rb
|
|
55
57
|
- lib/badline/address_bus/sid_slots.rb
|
|
56
|
-
- lib/badline/address_bus/ultimax_pages.rb
|
|
57
58
|
- lib/badline/addressable.rb
|
|
58
59
|
- lib/badline/audio.rb
|
|
59
60
|
- lib/badline/audio/aiff.rb
|
|
@@ -155,9 +156,11 @@ files:
|
|
|
155
156
|
- lib/badline/frontend/gamepads.rb
|
|
156
157
|
- lib/badline/frontend/info_view.rb
|
|
157
158
|
- lib/badline/frontend/keys.rb
|
|
159
|
+
- lib/badline/frontend/machine_window.rb
|
|
158
160
|
- lib/badline/frontend/menu_dialogs.rb
|
|
159
161
|
- lib/badline/frontend/menu_media.rb
|
|
160
162
|
- lib/badline/frontend/menu_pages.rb
|
|
163
|
+
- lib/badline/frontend/menu_ports.rb
|
|
161
164
|
- lib/badline/frontend/name_field.rb
|
|
162
165
|
- lib/badline/frontend/pacer.rb
|
|
163
166
|
- lib/badline/frontend/pad_port.rb
|
|
@@ -206,17 +209,22 @@ files:
|
|
|
206
209
|
- lib/badline/kernal_trap/drive/write_file.rb
|
|
207
210
|
- lib/badline/kernal_trap/drive/writes.rb
|
|
208
211
|
- lib/badline/kernal_trap/file.rb
|
|
212
|
+
- lib/badline/kernal_trap/layout.rb
|
|
209
213
|
- lib/badline/kernal_trap/load.rb
|
|
210
214
|
- lib/badline/kernal_trap/routine.rb
|
|
211
215
|
- lib/badline/kernal_trap/save.rb
|
|
212
216
|
- lib/badline/kernal_trap/serial.rb
|
|
213
217
|
- lib/badline/keyboard.rb
|
|
214
218
|
- lib/badline/keyboard_buffer.rb
|
|
219
|
+
- lib/badline/machine.rb
|
|
215
220
|
- lib/badline/media.rb
|
|
216
221
|
- lib/badline/media/disk_list.rb
|
|
217
222
|
- lib/badline/media/disk_set.rb
|
|
218
223
|
- lib/badline/media/queue.rb
|
|
219
224
|
- lib/badline/media/true_drive.rb
|
|
225
|
+
- lib/badline/media/vic20_basic.rb
|
|
226
|
+
- lib/badline/media/vic20_cartridge.rb
|
|
227
|
+
- lib/badline/media/vic20_media.rb
|
|
220
228
|
- lib/badline/memory.rb
|
|
221
229
|
- lib/badline/model.rb
|
|
222
230
|
- lib/badline/options.rb
|
|
@@ -240,13 +248,19 @@ files:
|
|
|
240
248
|
- lib/badline/rom.rb
|
|
241
249
|
- lib/badline/roms/README
|
|
242
250
|
- lib/badline/roms/basic.rom
|
|
251
|
+
- lib/badline/roms/c64gs.rom
|
|
243
252
|
- lib/badline/roms/character.rom
|
|
244
253
|
- lib/badline/roms/dos1541.rom
|
|
245
254
|
- lib/badline/roms/eapi/LICENSE.md
|
|
246
255
|
- lib/badline/roms/eapi/README
|
|
247
256
|
- lib/badline/roms/eapi/eapi-am29f040-14
|
|
257
|
+
- lib/badline/roms/kernal-pet64.rom
|
|
248
258
|
- lib/badline/roms/kernal-sx64.rom
|
|
249
259
|
- lib/badline/roms/kernal.rom
|
|
260
|
+
- lib/badline/roms/vic20/basic.rom
|
|
261
|
+
- lib/badline/roms/vic20/character.rom
|
|
262
|
+
- lib/badline/roms/vic20/kernal-ntsc.rom
|
|
263
|
+
- lib/badline/roms/vic20/kernal-pal.rom
|
|
250
264
|
- lib/badline/sdl.rb
|
|
251
265
|
- lib/badline/sid.rb
|
|
252
266
|
- lib/badline/sid/catch_up.rb
|
|
@@ -303,6 +317,7 @@ files:
|
|
|
303
317
|
- lib/badline/storage/tap.rb
|
|
304
318
|
- lib/badline/storage/unavailable.rb
|
|
305
319
|
- lib/badline/time_of_day.rb
|
|
320
|
+
- lib/badline/timing.rb
|
|
306
321
|
- lib/badline/traps.rb
|
|
307
322
|
- lib/badline/version.rb
|
|
308
323
|
- lib/badline/via.rb
|
|
@@ -333,6 +348,20 @@ files:
|
|
|
333
348
|
- lib/badline/vic/sprite/timing.rb
|
|
334
349
|
- lib/badline/vic/sprites.rb
|
|
335
350
|
- lib/badline/vic/sprites_state.rb
|
|
351
|
+
- lib/badline/vic20.rb
|
|
352
|
+
- lib/badline/vic20/attachments.rb
|
|
353
|
+
- lib/badline/vic20/bus.rb
|
|
354
|
+
- lib/badline/vic20/cpu.rb
|
|
355
|
+
- lib/badline/vic20/keyboard_via_ports.rb
|
|
356
|
+
- lib/badline/vic20/port_wiring.rb
|
|
357
|
+
- lib/badline/vic20/saved_state.rb
|
|
358
|
+
- lib/badline/vic20/sound.rb
|
|
359
|
+
- lib/badline/vic20/sound/output.rb
|
|
360
|
+
- lib/badline/vic20/user_via_ports.rb
|
|
361
|
+
- lib/badline/vic20/vic.rb
|
|
362
|
+
- lib/badline/vic20/vic/painter.rb
|
|
363
|
+
- lib/badline/vic20/vic/saved_state.rb
|
|
364
|
+
- lib/badline/vic20/vic/text_window.rb
|
|
336
365
|
- packaging/homebrew/badline.rb
|
|
337
366
|
homepage: https://github.com/elektronaut/badline
|
|
338
367
|
licenses:
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Badline
|
|
4
|
-
class AddressBus
|
|
5
|
-
# The page tables of an Ultimax cartridge, which AddressBus lays out
|
|
6
|
-
# in place of the $01 banking while the cartridge holds GAME low and
|
|
7
|
-
# EXROM high.
|
|
8
|
-
module UltimaxPages
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
# Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
|
|
12
|
-
# I/O always visible and open address space everywhere else. The ROML
|
|
13
|
-
# and ROMH selects fire on writes as well, so cartridge RAM or flash in
|
|
14
|
-
# either window takes the writes there.
|
|
15
|
-
def map_ultimax_pages
|
|
16
|
-
@read_pages.fill(@open_bus, 0x10, 0xf0)
|
|
17
|
-
@write_pages.fill(@open_bus, 0x10, 0xf0)
|
|
18
|
-
@read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
|
|
19
|
-
map_ultimax_writes(@cartridge.roml, 0x80)
|
|
20
|
-
@read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
|
|
21
|
-
map_ultimax_writes(@cartridge.romh, 0xe0)
|
|
22
|
-
map_ultimax_a000
|
|
23
|
-
map_io_pages
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def map_ultimax_writes(bank, first_page)
|
|
27
|
-
@write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def map_ultimax_a000
|
|
31
|
-
return unless (window = @cartridge.ultimax_a000)
|
|
32
|
-
|
|
33
|
-
@read_pages.fill(window, 0xa0, 0x20)
|
|
34
|
-
@write_pages.fill(window, 0xa0, 0x20)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|