badline 0.15.1 → 0.16.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 +55 -0
- data/CLAUDE.md +8 -7
- data/README.md +128 -9
- data/doc/media.md +9 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +215 -18
- data/doc/snapshots.md +55 -0
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/chips/crtc/keyed_state.rb +46 -0
- data/lib/badline/chips/crtc.rb +163 -0
- data/lib/badline/chips/pia/control_lines.rb +138 -0
- data/lib/badline/chips/pia/keyed_parts.rb +56 -0
- data/lib/badline/chips/pia.rb +138 -0
- data/lib/badline/chips/vic/register_log.rb +1 -4
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/ieee_bus.rb +74 -0
- data/lib/badline/drives/model1551/cable.rb +5 -2
- data/lib/badline/drives/model1551/tia.rb +13 -34
- data/lib/badline/drives/model1551.rb +26 -4
- data/lib/badline/drives/model2031/ieee_port.rb +133 -0
- data/lib/badline/drives/model2031/keyed_state.rb +66 -0
- data/lib/badline/drives/model2031/parts.rb +25 -0
- data/lib/badline/drives/model2031.rb +92 -0
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/controls.rb +23 -3
- data/lib/badline/frontend/gamepads.rb +10 -3
- data/lib/badline/frontend/keys.rb +88 -0
- data/lib/badline/frontend/media_slots.rb +1 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +27 -28
- data/lib/badline/frontend/menu/menu_pages.rb +24 -2
- data/lib/badline/frontend/menu/menu_ports.rb +22 -1
- data/lib/badline/frontend/menu/pause_menu.rb +1 -0
- data/lib/badline/frontend/pet_keyboard.rb +124 -0
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/timeline.rb +16 -9
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/dos/writes.rb +3 -1
- data/lib/badline/kernal_trap/dos.rb +6 -1
- data/lib/badline/kernal_trap/frames.rb +117 -0
- data/lib/badline/kernal_trap/ieee.rb +151 -0
- data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
- data/lib/badline/kernal_trap/keyed_state.rb +4 -4
- data/lib/badline/kernal_trap/routine.rb +3 -53
- data/lib/badline/kernal_trap/serial.rb +4 -102
- data/lib/badline/kernal_trap/steps.rb +64 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/machine/keyboard_buffer.rb +8 -0
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/extensions.rb +5 -2
- data/lib/badline/media/pet_media.rb +40 -0
- data/lib/badline/media/program.rb +14 -2
- data/lib/badline/media/true_drive.rb +19 -6
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +41 -17
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +23 -3
- data/lib/badline/options/table.rb +22 -4
- data/lib/badline/options/validation.rb +84 -5
- data/lib/badline/options.rb +47 -16
- data/lib/badline/pet/bus/expansion.rb +200 -0
- data/lib/badline/pet/bus.rb +222 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +32 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +155 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_drive.rb +76 -0
- data/lib/badline/pet/ieee_ports.rb +77 -0
- data/lib/badline/pet/ieee_traps.rb +80 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +88 -0
- data/lib/badline/pet/keyed_parts.rb +127 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +163 -0
- data/lib/badline/pet/rom_sets.rb +89 -0
- data/lib/badline/pet/sound.rb +128 -0
- data/lib/badline/pet/via.rb +10 -0
- data/lib/badline/pet/via_ports.rb +19 -0
- data/lib/badline/pet.rb +244 -0
- data/lib/badline/plus4/keyed_state.rb +5 -3
- data/lib/badline/plus4/tcbm_drive.rb +14 -0
- data/lib/badline/plus4.rb +1 -1
- data/lib/badline/roms/README +169 -0
- data/lib/badline/roms/dos2031.rom +0 -0
- data/lib/badline/roms/pet/basic1.rom +0 -0
- data/lib/badline/roms/pet/basic2.rom +0 -0
- data/lib/badline/roms/pet/basic4.rom +0 -0
- data/lib/badline/roms/pet/characters-din.rom +0 -0
- data/lib/badline/roms/pet/characters1.rom +0 -0
- data/lib/badline/roms/pet/characters2.rom +0 -0
- data/lib/badline/roms/pet/editor1.rom +0 -0
- data/lib/badline/roms/pet/editor2-b.rom +0 -0
- data/lib/badline/roms/pet/editor2-n.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-b.rom +0 -0
- data/lib/badline/roms/pet/editor4-n.rom +0 -0
- data/lib/badline/roms/pet/kernal1.rom +0 -0
- data/lib/badline/roms/pet/kernal2.rom +0 -0
- data/lib/badline/roms/pet/kernal4.rom +0 -0
- data/lib/badline/snapshot/container.rb +6 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +17 -3
- data/lib/badline/snapshot/image.rb +28 -10
- data/lib/badline/snapshot/pet_setup.rb +88 -0
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot/vice/pet.rb +117 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
- data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
- data/lib/badline/snapshot/vice/pet_via.rb +164 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +9 -5
- data/lib/badline/storage/d80_image.rb +48 -0
- data/lib/badline/storage/d82_image.rb +21 -0
- data/lib/badline/storage/tap.rb +16 -3
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/support/pet_checkpoint.rb +38 -0
- data/lib/badline/timing.rb +5 -4
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +7 -2
- data/lib/badline/vic20/bus/keyed_state.rb +5 -4
- data/lib/badline/vic20/bus.rb +5 -0
- data/lib/badline/vic20.rb +1 -1
- metadata +70 -1
data/lib/badline/options.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/pet/rom_sets"
|
|
3
4
|
require "badline/options/option"
|
|
4
5
|
require "badline/options/event"
|
|
5
6
|
require "badline/options/table"
|
|
@@ -12,10 +13,12 @@ module Badline
|
|
|
12
13
|
# Either opens the window for any media, or with --headless or
|
|
13
14
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
14
15
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
15
|
-
# `c64`, `vic20`, `c128` or `
|
|
16
|
-
# family, and without one the family is the C64,
|
|
17
|
-
# that loads where C128 BASIC starts
|
|
18
|
-
#
|
|
16
|
+
# `c64`, `vic20`, `c128`, `plus4` or `pet`, given first, names the
|
|
17
|
+
# machine family, and without one the family is the C64, the C128 for a
|
|
18
|
+
# program that loads where C128 BASIC starts, or the PET for an 8050's
|
|
19
|
+
# or 8250's disk or a PET tape.
|
|
20
|
+
# The native build adds --no-sound and --version, and badline-ruby adds
|
|
21
|
+
# --disable-jit.
|
|
19
22
|
#
|
|
20
23
|
# TABLE lists the options, and both the parser and the help read it. It
|
|
21
24
|
# parses by hand, inside the subset of Ruby Spinel compiles. Values come
|
|
@@ -51,6 +54,9 @@ module Badline
|
|
|
51
54
|
# The Plus/4's RAM sizes as --ram names them, in K.
|
|
52
55
|
PLUS4_RAM = { "16k" => 16, "32k" => 32, "64k" => 64 }.freeze
|
|
53
56
|
|
|
57
|
+
# The PET's, below $8000.
|
|
58
|
+
PET_RAM = { "4k" => 4, "8k" => 8, "16k" => 16, "32k" => 32 }.freeze
|
|
59
|
+
|
|
54
60
|
# Where --sid-card-address puts the Plus/4's SID card.
|
|
55
61
|
SID_CARD_ADDRESSES = { "fd40" => 0xfd40, "fe80" => 0xfe80 }.freeze
|
|
56
62
|
|
|
@@ -70,16 +76,24 @@ module Badline
|
|
|
70
76
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
71
77
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
72
78
|
|
|
73
|
-
# The machine family to build (Badline::Machine), :c64, :vic20, :c128
|
|
74
|
-
# or :
|
|
79
|
+
# The machine family to build (Badline::Machine), :c64, :vic20, :c128,
|
|
80
|
+
# :plus4 or :pet.
|
|
75
81
|
attr_reader :family
|
|
76
82
|
|
|
83
|
+
# The ROMs --basic, --editor and --characters fit a PET with, keys of
|
|
84
|
+
# PET::ROMSets::BASICS, PET::ROMSets::EDITORS and
|
|
85
|
+
# PET::ROMSets::CHARACTERS, or nil for its own.
|
|
86
|
+
attr_reader :basic, :editor, :characters
|
|
87
|
+
|
|
88
|
+
# The 8296's board jumpers --jumpers names (PET::Bus::Expansion::JUMPERS).
|
|
89
|
+
attr_reader :jumpers
|
|
90
|
+
|
|
77
91
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
78
92
|
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
79
93
|
attr_reader :ram
|
|
80
94
|
|
|
81
|
-
# The Plus/4's RAM in K --ram names, one of PLUS4_RAM's
|
|
82
|
-
# --ram.
|
|
95
|
+
# The Plus/4's or the PET's RAM in K --ram names, one of PLUS4_RAM's or
|
|
96
|
+
# PET_RAM's, or nil without --ram.
|
|
83
97
|
attr_reader :ram_kb
|
|
84
98
|
|
|
85
99
|
# The Plus/4's cartridge ROM images --c1lo, --c1hi, --c2lo and --c2hi
|
|
@@ -121,6 +135,8 @@ module Badline
|
|
|
121
135
|
@reu = nil
|
|
122
136
|
@ram = nil
|
|
123
137
|
@ram_kb = nil
|
|
138
|
+
@ram_name = nil
|
|
139
|
+
initialize_pet
|
|
124
140
|
@cartridges = {}
|
|
125
141
|
@autostart = true
|
|
126
142
|
@writable = false
|
|
@@ -229,6 +245,10 @@ module Badline
|
|
|
229
245
|
# power-on.
|
|
230
246
|
def c64_mode? = @c64_mode
|
|
231
247
|
|
|
248
|
+
# The frame rate of a CRTC PET's editor: 60 with --60hz or a 60 Hz
|
|
249
|
+
# --editor, else 50.
|
|
250
|
+
def refresh_hz = @refresh_hz || 50
|
|
251
|
+
|
|
232
252
|
def help? = @help
|
|
233
253
|
|
|
234
254
|
def version? = @version
|
|
@@ -244,7 +264,7 @@ module Badline
|
|
|
244
264
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
245
265
|
# option of the emulator's window asks for the machine.
|
|
246
266
|
def play_lone_tune
|
|
247
|
-
return if @sid_command || %i[vic20 plus4].include?(@family) || headless? || !@window_only.empty?
|
|
267
|
+
return if @sid_command || %i[vic20 plus4 pet].include?(@family) || headless? || !@window_only.empty?
|
|
248
268
|
return if @media_path.nil?
|
|
249
269
|
|
|
250
270
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
@@ -303,7 +323,8 @@ module Badline
|
|
|
303
323
|
when "--ram-expansion" then @ram_expansion = plus4_ram_expansion(value)
|
|
304
324
|
when "--model" then @models << value
|
|
305
325
|
when "--reu" then @reu = reu_size(value)
|
|
306
|
-
when "--ram" then
|
|
326
|
+
when "--ram" then @ram_name = value
|
|
327
|
+
when "--basic", "--editor", "--characters", "--jumpers" then pet_option(name, value)
|
|
307
328
|
when "--drive-type" then @drive_type = drive_type_for(value)
|
|
308
329
|
when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
|
|
309
330
|
when "--frames" then @frames = number(flag, value)
|
|
@@ -318,12 +339,21 @@ module Badline
|
|
|
318
339
|
end
|
|
319
340
|
end
|
|
320
341
|
|
|
321
|
-
#
|
|
322
|
-
def
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
# The PET's frame rate, ROMs and board jumpers, its own until named.
|
|
343
|
+
def initialize_pet
|
|
344
|
+
@refresh_hz = nil
|
|
345
|
+
@basic = nil
|
|
346
|
+
@editor = nil
|
|
347
|
+
@characters = nil
|
|
348
|
+
@jumpers = []
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def pet_option(name, value)
|
|
352
|
+
case name
|
|
353
|
+
when "--basic" then @basic = value
|
|
354
|
+
when "--editor" then @editor = value
|
|
355
|
+
when "--characters" then @characters = value
|
|
356
|
+
else @jumpers = value.split(",")
|
|
327
357
|
end
|
|
328
358
|
end
|
|
329
359
|
|
|
@@ -339,6 +369,7 @@ module Badline
|
|
|
339
369
|
when "--vice" then @vice = true
|
|
340
370
|
when "--ntsc" then @models << "ntsc"
|
|
341
371
|
when "--c64" then @c64_mode = true
|
|
372
|
+
when "--60hz" then @refresh_hz = 60
|
|
342
373
|
when "--unpaced" then @paced = false
|
|
343
374
|
when "--disable-jit" then @jit = false
|
|
344
375
|
when "--help" then @help = true
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class Bus
|
|
6
|
+
# The 64K board of the 8096 and the 8296: four 16K banks, which a
|
|
7
|
+
# write-only control register at $FFF0 lays over $8000-$FFFF, two at
|
|
8
|
+
# a time:
|
|
9
|
+
#
|
|
10
|
+
# bit 7 the board on; when clear, the rest ignored
|
|
11
|
+
# bit 6 I/O shows through at $E800-$EFFF
|
|
12
|
+
# bit 5 the screen shows through at $8000-$8FFF
|
|
13
|
+
# bit 3 bank 3 at $C000-$FFFF, else bank 1
|
|
14
|
+
# bit 2 bank 2 at $8000-$BFFF, else bank 0
|
|
15
|
+
# bit 1 writes to $C000-$FFFF go nowhere
|
|
16
|
+
# bit 0 writes to $8000-$BFFF go nowhere
|
|
17
|
+
#
|
|
18
|
+
# A write to $FFF0 lands where $FFF0 was before it, too. The RES line
|
|
19
|
+
# clears the register, so the 8096 starts as an 8032.
|
|
20
|
+
#
|
|
21
|
+
# The 8296 decodes its memory in a PLA, after the logic equations
|
|
22
|
+
# André Fachat read out of it (8296desc.txt on zimmers.net), with 32K
|
|
23
|
+
# of RAM from $8000 (Bus#screen) besides the board:
|
|
24
|
+
#
|
|
25
|
+
# - With bit 7 clear, every write from $8000 up but to $E8xx goes to
|
|
26
|
+
# that RAM, under the ROMs too. Reads find the screen at $8000,
|
|
27
|
+
# the empty sockets at $9000 and $A000, BASIC, the editor at
|
|
28
|
+
# $E000-$E7FF and again at $E900-$EFFF, and the KERNAL, unless the
|
|
29
|
+
# lines /RAMSEL9, /RAMSELA and /RAMON put RAM there (#ram_lines).
|
|
30
|
+
# RAMSEL9 puts it at $9000, RAMSELA at $A000, RAMON at
|
|
31
|
+
# $8000-$DFFF, RAMON with either of the others at $E000-$EFFF but
|
|
32
|
+
# $E8xx, and RAMON with RAMSELA at $F000 as well.
|
|
33
|
+
# - With bit 7 set, I/O showing through brings back the editor at
|
|
34
|
+
# $E900-$EFFF, which an 8096 leaves empty.
|
|
35
|
+
#
|
|
36
|
+
# Jumpers tie the lines: JU2 RAMSEL9 low and JU1 RAMSELA low
|
|
37
|
+
# (:ram9, :rama), or JU4, JU3 and JU5 RAMSEL9, RAMSELA and RAMON to
|
|
38
|
+
# the user port's PA1, PA0 and PA2 (:userport), each active low.
|
|
39
|
+
# With none fitted, as on xpet's 8296, the lines stay high.
|
|
40
|
+
class Expansion
|
|
41
|
+
# A 16K bank of the board, seen from +start+.
|
|
42
|
+
class Window
|
|
43
|
+
attr_accessor :read_only
|
|
44
|
+
|
|
45
|
+
def initialize(cells, offset, start)
|
|
46
|
+
@cells = cells
|
|
47
|
+
@offset = offset - start
|
|
48
|
+
@read_only = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def peek(addr) = @cells[addr + @offset]
|
|
52
|
+
|
|
53
|
+
def poke(addr, value)
|
|
54
|
+
@cells[addr + @offset] = value unless @read_only
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Page $FF's writes, which pass to what the page holds and set the
|
|
59
|
+
# control register at $FFF0.
|
|
60
|
+
class ControlPort
|
|
61
|
+
attr_accessor :under
|
|
62
|
+
|
|
63
|
+
def initialize(expansion, under)
|
|
64
|
+
@expansion = expansion
|
|
65
|
+
@under = under
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def poke(addr, value)
|
|
69
|
+
@under.poke(addr, value)
|
|
70
|
+
@expansion.control = value if addr == 0xfff0
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The user port lines that RAMSELA, RAMSEL9 and RAMON follow.
|
|
75
|
+
USER_PORT_LINES = 0x07
|
|
76
|
+
|
|
77
|
+
attr_reader :cells, :control
|
|
78
|
+
|
|
79
|
+
# The user port's PA0-PA2 as the board last saw them.
|
|
80
|
+
attr_reader :lines
|
|
81
|
+
|
|
82
|
+
# +jumpers+ are the 8296's (ROMSets::JUMPERS) when +board8296+ is.
|
|
83
|
+
def initialize(bus, board8296: false, jumpers: [])
|
|
84
|
+
@bus = bus
|
|
85
|
+
@cells = Array.new(0x10000, 0)
|
|
86
|
+
@banks = [0x8000, 0xc000, 0x8000, 0xc000].each_with_index.map do |start, bank|
|
|
87
|
+
Window.new(@cells, bank * 0x4000, start)
|
|
88
|
+
end
|
|
89
|
+
@port = ControlPort.new(self, @banks[1])
|
|
90
|
+
@control = 0
|
|
91
|
+
@board8296 = board8296
|
|
92
|
+
@ram9 = jumpers.include?("ram9")
|
|
93
|
+
@rama = jumpers.include?("rama")
|
|
94
|
+
@user_port = jumpers.include?("userport")
|
|
95
|
+
@lines = USER_PORT_LINES
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def board8296? = @board8296
|
|
99
|
+
|
|
100
|
+
# Whether the board follows the user port's lines.
|
|
101
|
+
def user_port? = @user_port
|
|
102
|
+
|
|
103
|
+
def control=(value)
|
|
104
|
+
@control = value
|
|
105
|
+
@bus.map_pages
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Follows the user port's lines, +port_a+ as the VIA drives them.
|
|
109
|
+
def lines=(port_a)
|
|
110
|
+
lines = port_a & USER_PORT_LINES
|
|
111
|
+
return if lines == @lines
|
|
112
|
+
|
|
113
|
+
@lines = lines
|
|
114
|
+
@bus.map_pages
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def reset!
|
|
118
|
+
self.control = 0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def power_on!
|
|
122
|
+
@cells.fill(0)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Whether the CPU reads the KERNAL at $F000-$FFFF.
|
|
126
|
+
def kernal_mapped?
|
|
127
|
+
return false if @control.anybits?(0x80)
|
|
128
|
+
|
|
129
|
+
!(@board8296 && ram_on? && ram_a?)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Lays the banks the register picks over the pages, and the
|
|
133
|
+
# control register over page $FF's writes.
|
|
134
|
+
def map(reads, writes)
|
|
135
|
+
if @control.anybits?(0x80)
|
|
136
|
+
map_banks(reads, writes)
|
|
137
|
+
elsif @board8296
|
|
138
|
+
map_ram8296(reads, writes)
|
|
139
|
+
end
|
|
140
|
+
@port.under = writes[0xff]
|
|
141
|
+
writes[0xff] = @port
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def ram_on? = @user_port && @lines.nobits?(0x04)
|
|
147
|
+
|
|
148
|
+
def ram9? = @ram9 || (@user_port && @lines.nobits?(0x02))
|
|
149
|
+
|
|
150
|
+
def ram_a? = @rama || (@user_port && @lines.nobits?(0x01))
|
|
151
|
+
|
|
152
|
+
def map_banks(reads, writes)
|
|
153
|
+
low = @banks[@control.anybits?(0x04) ? 2 : 0]
|
|
154
|
+
high = @banks[@control.anybits?(0x08) ? 3 : 1]
|
|
155
|
+
low.read_only = @control.anybits?(0x01)
|
|
156
|
+
high.read_only = @control.anybits?(0x02)
|
|
157
|
+
first = @control.anybits?(0x20) ? 0x90 : 0x80
|
|
158
|
+
reads.fill(low, first, 0xc0 - first)
|
|
159
|
+
writes.fill(low, first, 0xc0 - first)
|
|
160
|
+
[[0xc0, 0x28], [0xe8, 0x08], [0xf0, 0x10]].each do |page, count|
|
|
161
|
+
next if page == 0xe8 && @control.anybits?(0x40)
|
|
162
|
+
|
|
163
|
+
reads.fill(high, page, count)
|
|
164
|
+
writes.fill(high, page, count)
|
|
165
|
+
end
|
|
166
|
+
map_editor_through(reads, writes) if @board8296
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# I/O showing through on the 8296 leaves $E900-$EFFF to the editor,
|
|
170
|
+
# and its writes to nothing.
|
|
171
|
+
def map_editor_through(reads, writes)
|
|
172
|
+
return unless @control.anybits?(0x40)
|
|
173
|
+
|
|
174
|
+
reads.fill(@bus.editor_rom, 0xe9, 0x07)
|
|
175
|
+
writes.fill(@bus.open_bus, 0xe9, 0x07)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def map_ram8296(reads, writes)
|
|
179
|
+
ram = @bus.screen
|
|
180
|
+
writes.fill(ram, 0x90, 0x58)
|
|
181
|
+
writes.fill(ram, 0xe9, 0x17)
|
|
182
|
+
reads.fill(@bus.editor_rom, 0xe9, 0x07)
|
|
183
|
+
ram_on = ram_on?
|
|
184
|
+
ram9 = ram9?
|
|
185
|
+
ram_a = ram_a?
|
|
186
|
+
reads.fill(ram, 0x90, 0x10) if ram9 || ram_on
|
|
187
|
+
reads.fill(ram, 0xa0, 0x10) if ram_a || ram_on
|
|
188
|
+
return unless ram_on
|
|
189
|
+
|
|
190
|
+
reads.fill(ram, 0xb0, 0x30)
|
|
191
|
+
return unless ram9 || ram_a
|
|
192
|
+
|
|
193
|
+
reads.fill(ram, 0xe0, 0x08)
|
|
194
|
+
reads.fill(ram, 0xe9, 0x07)
|
|
195
|
+
reads.fill(ram, 0xf0, 0x10) if ram_a
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/pet/bus/expansion"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class PET
|
|
7
|
+
# The CPU's bus:
|
|
8
|
+
#
|
|
9
|
+
# $0000-$7FFF - RAM, 4K, 8K, 16K or 32K of it from $0000, then nothing
|
|
10
|
+
# $8000-$8FFF - screen RAM, repeated (Model), then nothing
|
|
11
|
+
# $9000-$AFFF - two empty ROM sockets
|
|
12
|
+
# $B000-$DFFF - BASIC 4, or BASIC 1 or 2 from $C000
|
|
13
|
+
# $E000-$E7FF - the screen editor ROM
|
|
14
|
+
# $E800-$E8FF - I/O (IO)
|
|
15
|
+
# $E900-$EFFF - nothing
|
|
16
|
+
# $F000-$FFFF - the KERNAL ROM
|
|
17
|
+
#
|
|
18
|
+
# The data lines hold the last byte they carried, so a read from an
|
|
19
|
+
# empty spot finds it, mostly the high byte of the address just
|
|
20
|
+
# fetched, as PET/unconnected shows on a real 3016. Writes to ROM and
|
|
21
|
+
# to empty spots go nowhere. An 8096 banks its 64K board over
|
|
22
|
+
# $8000-$FFFF (Expansion), and an 8296 its board and the RAM under its
|
|
23
|
+
# ROMs.
|
|
24
|
+
class Bus
|
|
25
|
+
include Addressable
|
|
26
|
+
|
|
27
|
+
# The last byte the CPU moved, which an empty spot reads back.
|
|
28
|
+
class OpenBus
|
|
29
|
+
def initialize(bus)
|
|
30
|
+
@bus = bus
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def peek(_addr) = @bus.data
|
|
34
|
+
def poke(_addr, _value); end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Screen RAM, repeated over the pages it answers in. On the 8296 it is
|
|
38
|
+
# all 32K of RAM from $8000, which the board lays under the ROMs.
|
|
39
|
+
class ScreenRAM
|
|
40
|
+
attr_reader :cells
|
|
41
|
+
|
|
42
|
+
def initialize(size)
|
|
43
|
+
@cells = Array.new(size, 0)
|
|
44
|
+
@mask = size - 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def peek(addr) = @cells[addr & @mask]
|
|
48
|
+
|
|
49
|
+
def poke(addr, value)
|
|
50
|
+
@cells[addr & @mask] = value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# $E800-$E8FF. Each chip answers on one address line, with its
|
|
55
|
+
# registers repeated: A4 selects PIA 1, A5 PIA 2, A6 the VIA and A7
|
|
56
|
+
# the CRTC, where there is one. Where more than one chip answers a
|
|
57
|
+
# read, each pulls the lines it drives low, so the CPU reads the AND
|
|
58
|
+
# of them, and a write reaches them all. Where none answers, as in
|
|
59
|
+
# $E800-$E80F, the CPU reads the last byte on the bus.
|
|
60
|
+
class IO
|
|
61
|
+
def initialize(bus, pia1:, pia2:, via:, crtc:, crtc_fitted:)
|
|
62
|
+
@bus = bus
|
|
63
|
+
@pia1 = pia1
|
|
64
|
+
@pia2 = pia2
|
|
65
|
+
@via = via
|
|
66
|
+
@crtc = crtc
|
|
67
|
+
@lines = crtc_fitted ? 0xf0 : 0x70
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def peek(addr)
|
|
71
|
+
return @bus.data if addr.nobits?(@lines)
|
|
72
|
+
|
|
73
|
+
value = 0xff
|
|
74
|
+
value &= @pia1.peek(addr) if addr.anybits?(0x10)
|
|
75
|
+
value &= @pia2.peek(addr) if addr.anybits?(0x20)
|
|
76
|
+
value &= @via.peek(addr) if addr.anybits?(0x40)
|
|
77
|
+
value &= @crtc.peek(addr) if addr.anybits?(@lines & 0x80)
|
|
78
|
+
value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def poke(addr, value)
|
|
82
|
+
@pia1.poke(addr, value) if addr.anybits?(0x10)
|
|
83
|
+
@pia2.poke(addr, value) if addr.anybits?(0x20)
|
|
84
|
+
@via.poke(addr, value) if addr.anybits?(0x40)
|
|
85
|
+
@crtc.poke(addr, value) if addr.anybits?(@lines & 0x80)
|
|
86
|
+
@bus.io_written
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# VICE's debug cartridge register at $8BFF, which a write calls the
|
|
91
|
+
# handler with, the exit code the VICE testprogs report. It answers
|
|
92
|
+
# on every model, ahead of the 2001's screen RAM there and the
|
|
93
|
+
# 8096's banks, and every other write to its page passes to what
|
|
94
|
+
# the page holds.
|
|
95
|
+
class DebugRegister
|
|
96
|
+
ADDRESS = 0x8bff
|
|
97
|
+
|
|
98
|
+
attr_accessor :under
|
|
99
|
+
|
|
100
|
+
def initialize(&handler)
|
|
101
|
+
@handler = handler
|
|
102
|
+
@under = nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def poke(addr, value)
|
|
106
|
+
return @handler.call(value) if addr == ADDRESS
|
|
107
|
+
|
|
108
|
+
@under.poke(addr, value)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
attr_reader :ram, :screen, :expansion, :editor_rom, :open_bus
|
|
113
|
+
|
|
114
|
+
# The last byte the CPU read or wrote.
|
|
115
|
+
attr_reader :data
|
|
116
|
+
|
|
117
|
+
# +model+ is a Model::Profile, +ram_kb+ the RAM below $8000 and
|
|
118
|
+
# +editor+ the editor ROM's file. The CRTC answers only on a board
|
|
119
|
+
# the model fits one on. +jumpers+ are the 8296's (ROMSets::JUMPERS).
|
|
120
|
+
def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:, jumpers: [])
|
|
121
|
+
addressable_at(0, length: 2**16)
|
|
122
|
+
@ram = Memory.new([], length: ram_kb * 1024, start: 0)
|
|
123
|
+
@screen = ScreenRAM.new(model.screen_bytes)
|
|
124
|
+
@screen_pages = model.screen_pages
|
|
125
|
+
basic = ROM.read(model.basic)
|
|
126
|
+
@basic_rom = ROM.new(basic, length: basic.length, start: 0xe000 - basic.length)
|
|
127
|
+
@editor_rom = model.cbm8296? ? editor_socket(editor) : ROM.load(editor, 0xe000)
|
|
128
|
+
@kernal_rom = ROM.load(model.kernal, 0xf000)
|
|
129
|
+
@io = IO.new(self, pia1:, pia2:, via:, crtc:, crtc_fitted: model.crtc?)
|
|
130
|
+
@open_bus = OpenBus.new(self)
|
|
131
|
+
@data = 0
|
|
132
|
+
@read_pages = Array.new(256, @open_bus)
|
|
133
|
+
@write_pages = Array.new(256, @open_bus)
|
|
134
|
+
@expansion = model.expansion ? Expansion.new(self, board8296: model.cbm8296?, jumpers:) : nil
|
|
135
|
+
@via = via
|
|
136
|
+
@user_port = !@expansion.nil? && @expansion.user_port?
|
|
137
|
+
@debug_register = nil
|
|
138
|
+
@io_write_handler = nil
|
|
139
|
+
map_pages
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Calls the block after each write to the I/O chips, which may have
|
|
143
|
+
# moved the lines their ports drive.
|
|
144
|
+
def on_io_write(&handler)
|
|
145
|
+
@io_write_handler = handler
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def io_written
|
|
149
|
+
@io_write_handler&.call
|
|
150
|
+
@expansion.lines = @via.port_a_output if @user_port
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# The RES line reaches the board's control register, and the user
|
|
154
|
+
# port's lines float high with the VIA's.
|
|
155
|
+
def reset!
|
|
156
|
+
return if @expansion.nil?
|
|
157
|
+
|
|
158
|
+
@expansion.reset!
|
|
159
|
+
@expansion.lines = @via.port_a_output if @user_port
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def power_on!
|
|
163
|
+
@ram.clear!
|
|
164
|
+
@screen.cells.fill(0)
|
|
165
|
+
@expansion&.power_on!
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def peek(addr)
|
|
169
|
+
@data = @read_pages[addr >> 8].peek(addr)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def poke(addr, value)
|
|
173
|
+
@data = value
|
|
174
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Whether the CPU reads the KERNAL ROM at $F000-$FFFF, which an 8096's
|
|
178
|
+
# board covers while its control register turns it on, and an 8296's
|
|
179
|
+
# RAM too.
|
|
180
|
+
def kernal_mapped? = @expansion.nil? || @expansion.kernal_mapped?
|
|
181
|
+
|
|
182
|
+
# Calls the block with each byte written to $8BFF (DebugRegister).
|
|
183
|
+
def install_debug_register(&)
|
|
184
|
+
@debug_register = DebugRegister.new(&)
|
|
185
|
+
map_pages
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Lays the pages out again, for the board's control register.
|
|
189
|
+
def map_pages
|
|
190
|
+
map(@open_bus, 0x00, 0x100)
|
|
191
|
+
map(@ram, 0x00, @ram.length >> 8)
|
|
192
|
+
map(@screen, 0x80, @screen_pages)
|
|
193
|
+
@read_pages.fill(@basic_rom, @basic_rom.start >> 8, @basic_rom.length >> 8)
|
|
194
|
+
@read_pages.fill(@editor_rom, 0xe0, 0x08)
|
|
195
|
+
map(@io, 0xe8, 0x01)
|
|
196
|
+
@read_pages.fill(@kernal_rom, 0xf0, 0x10)
|
|
197
|
+
@expansion&.map(@read_pages, @write_pages)
|
|
198
|
+
map_debug_register if @debug_register
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# The 8296's editor socket, which answers at $E000-$E7FF and
|
|
204
|
+
# $E900-$EFFF: a 4K ROM's halves there, or a 2K one twice over.
|
|
205
|
+
def editor_socket(file)
|
|
206
|
+
bytes = ROM.read(file)
|
|
207
|
+
bytes += bytes if bytes.length == 0x800
|
|
208
|
+
ROM.new(bytes, length: 0x1000, start: 0xe000)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def map_debug_register
|
|
212
|
+
@debug_register.under = @write_pages[0x8b]
|
|
213
|
+
@write_pages[0x8b] = @debug_register
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def map(chip, first, count)
|
|
217
|
+
@read_pages.fill(chip, first, count)
|
|
218
|
+
@write_pages.fill(chip, first, count)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
# The picture a front end shows, a palette index a dot: 0 for black and
|
|
6
|
+
# 1 for the lit phosphor. The video paints it eight dots at a time, one
|
|
7
|
+
# character ROM row, and leaves alone the dots that would not change:
|
|
8
|
+
# each four dots remember the row last painted from them, and a
|
|
9
|
+
# painting that overlaps theirs from elsewhere makes them forget it.
|
|
10
|
+
#
|
|
11
|
+
# Each line keeps the span its last painting covered. When a line's
|
|
12
|
+
# span changes, the dots it covered before and doesn't now go black.
|
|
13
|
+
class Canvas
|
|
14
|
+
# Black and the green of a P1 phosphor, as the VICE testbench's
|
|
15
|
+
# references draw them.
|
|
16
|
+
PALETTE = [0x000000, 0x41ff00].freeze
|
|
17
|
+
|
|
18
|
+
# The eight dots of each ROM row, the leftmost in bit 7.
|
|
19
|
+
DOTS = Array.new(256) { |bits| Array.new(8) { |dot| (bits >> (7 - dot)) & 1 } }.freeze
|
|
20
|
+
|
|
21
|
+
attr_reader :display, :width, :height, :dirty_lines
|
|
22
|
+
|
|
23
|
+
# Nothing paints within +margin+ dots of either side.
|
|
24
|
+
def initialize(width, height, margin: 0)
|
|
25
|
+
@first = margin
|
|
26
|
+
@last = width - 8 - margin
|
|
27
|
+
@width = width
|
|
28
|
+
@height = height
|
|
29
|
+
@display = Array.new(width * height, 0)
|
|
30
|
+
@dirty_lines = Array.new(height, true)
|
|
31
|
+
@slot_width = width >> 2
|
|
32
|
+
@painted = Array.new(@slot_width * height, 0)
|
|
33
|
+
@span_from = Array.new(height, 0)
|
|
34
|
+
@span_to = Array.new(height, 0)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def palette = PALETTE
|
|
38
|
+
|
|
39
|
+
def clear_dirty_lines!
|
|
40
|
+
@dirty_lines.fill(false)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Paints `bits` on `line` from dot `column` on, if they fit.
|
|
44
|
+
def paint(column, line, bits)
|
|
45
|
+
return if column < @first || column > @last
|
|
46
|
+
|
|
47
|
+
slot = (line * @slot_width) + (column >> 2)
|
|
48
|
+
return if @painted[slot] == bits
|
|
49
|
+
|
|
50
|
+
@painted[slot] = bits
|
|
51
|
+
@painted[slot - 1] = -1 if slot.positive?
|
|
52
|
+
@painted[slot + 1] = -1 if slot + 1 < @painted.length
|
|
53
|
+
@display[(line * @width) + column, 8] = DOTS[bits]
|
|
54
|
+
@dirty_lines[line] = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Sets `line`'s span to the dots from `from` up to `to`, none if `to`
|
|
58
|
+
# isn't past `from`, and blacks out what its last one covered beyond
|
|
59
|
+
# it.
|
|
60
|
+
def span(line, from, to)
|
|
61
|
+
width = @width
|
|
62
|
+
from = 0 if from.negative?
|
|
63
|
+
to = width if to > width
|
|
64
|
+
from = to = 0 if to <= from
|
|
65
|
+
old_from = @span_from[line]
|
|
66
|
+
old_to = @span_to[line]
|
|
67
|
+
return if from == old_from && to == old_to
|
|
68
|
+
|
|
69
|
+
blank(line, old_from, [from, old_to].min) if old_from < from
|
|
70
|
+
blank(line, [to, old_from].max, old_to) if old_to > to
|
|
71
|
+
@span_from[line] = from
|
|
72
|
+
@span_to[line] = to
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Blacks out every dot, as at power on.
|
|
76
|
+
def clear!
|
|
77
|
+
@display.fill(0)
|
|
78
|
+
@painted.fill(0)
|
|
79
|
+
@span_from.fill(0)
|
|
80
|
+
@span_to.fill(0)
|
|
81
|
+
@dirty_lines.fill(true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def blank(line, from, to)
|
|
87
|
+
return if to <= from
|
|
88
|
+
|
|
89
|
+
@display.fill(0, (line * @width) + from, to - from)
|
|
90
|
+
first = (line * @slot_width) + (from >> 2)
|
|
91
|
+
@painted.fill(0, first, ((to + 3) >> 2) - (from >> 2))
|
|
92
|
+
@painted[first - 1] = -1 if from >= 4
|
|
93
|
+
@dirty_lines[line] = true
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|