badline 0.15.1 → 0.16.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 +39 -0
- data/CLAUDE.md +8 -7
- data/README.md +102 -6
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +213 -18
- data/doc/snapshots.md +14 -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 +28 -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.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 +15 -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 +148 -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 +28 -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/vic20_media.rb +2 -2
- data/lib/badline/media.rb +36 -16
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +19 -3
- data/lib/badline/options/table.rb +14 -2
- data/lib/badline/options/validation.rb +68 -5
- data/lib/badline/options.rb +31 -19
- data/lib/badline/pet/bus/expansion.rb +106 -0
- data/lib/badline/pet/bus.rb +204 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +26 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +142 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_ports.rb +66 -0
- data/lib/badline/pet/ieee_traps.rb +75 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +85 -0
- data/lib/badline/pet/keyed_parts.rb +123 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +113 -0
- data/lib/badline/pet/rom_sets.rb +65 -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 +226 -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 +137 -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/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.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 +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +16 -3
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- 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 +55 -1
data/doc/snapshots.md
CHANGED
|
@@ -140,6 +140,20 @@ KERNAL and function ROMs come with the model. Its file names its machine
|
|
|
140
140
|
doesn't open it, and badline doesn't open xplus4's. `--vice` and
|
|
141
141
|
`computer.export_snapshot(path)` refuse a Plus/4.
|
|
142
142
|
|
|
143
|
+
## The PET
|
|
144
|
+
|
|
145
|
+
Every PET and CBM model saves and restores the same way. A snapshot of
|
|
146
|
+
one holds its RAM, the screen RAM, an 8096's 64K board with its control
|
|
147
|
+
register, both PIAs, the VIA, the CPU, the CRTC on the boards that have
|
|
148
|
+
one, the picture with where the video is in its frame, the tapes in both
|
|
149
|
+
datasettes with their places on them, and the disk or directory device 8
|
|
150
|
+
serves through the IEEE-488 traps. It restores into a machine of the same
|
|
151
|
+
model with the same RAM, the same editor frame rate and the same ROMs,
|
|
152
|
+
which come with the model. Its file names its machine `PET`, as xpet's
|
|
153
|
+
do, and holds only badline's modules, so xpet doesn't open it, and
|
|
154
|
+
badline doesn't open xpet's. `--vice` and
|
|
155
|
+
`computer.export_snapshot(path)` refuse a PET.
|
|
156
|
+
|
|
143
157
|
## VICE
|
|
144
158
|
|
|
145
159
|
Snapshots use VICE's `.vsf` format, and badline's own modules are named
|
data/lib/badline/c64/common.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Badline
|
|
|
10
10
|
def timing
|
|
11
11
|
region = address_bus.region
|
|
12
12
|
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
13
|
-
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
13
|
+
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1, dots: 1)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def load_prg(data)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class CRTC
|
|
6
|
+
# The CRTC's fields in a keyed snapshot module (Snapshot::Keyed): the
|
|
7
|
+
# registers, the one selected, and the counters: the character, the
|
|
8
|
+
# characters since horizontal sync, the line, the row, the lines of
|
|
9
|
+
# vertical sync left, whether the frame is in its vertical adjust,
|
|
10
|
+
# whether the row and the character show, and the memory address with
|
|
11
|
+
# the row's start. The output restores its own state.
|
|
12
|
+
module KeyedState
|
|
13
|
+
def save_fields(out)
|
|
14
|
+
out.bytes("registers", @registers).int("selected", @selected).int("column", @column)
|
|
15
|
+
out.int("since_hsync", @since_hsync).int("line", @line).int("row", @row).int("vsync_lines", @vsync_lines)
|
|
16
|
+
out.bool("adjusting", @adjusting).bool("row_display", @row_display).bool("column_display", @column_display)
|
|
17
|
+
out.int("row_start", @row_start).int("address", @address)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def load_fields(input)
|
|
21
|
+
input.bytes_into("registers", @registers)
|
|
22
|
+
MASKS.each_with_index { |mask, index| @registers[index] &= mask }
|
|
23
|
+
@selected = input.masked("selected", @selected, 0x1f)
|
|
24
|
+
@column = input.byte("column", @column)
|
|
25
|
+
@since_hsync = input.int("since_hsync", @since_hsync)
|
|
26
|
+
@line = input.masked("line", @line, 0x1f)
|
|
27
|
+
@row = input.masked("row", @row, 0x7f)
|
|
28
|
+
@vsync_lines = input.int("vsync_lines", @vsync_lines)
|
|
29
|
+
load_flag_fields(input)
|
|
30
|
+
@row_start = input.masked("row_start", @row_start, 0x3fff)
|
|
31
|
+
@address = input.masked("address", @address, 0x3fff)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def load_flag_fields(input)
|
|
37
|
+
@adjusting = input.bool("adjusting", @adjusting)
|
|
38
|
+
@row_display = input.bool("row_display", @row_display)
|
|
39
|
+
@column_display = input.bool("column_display", @column_display)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
include KeyedState
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/crtc/keyed_state"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Chips
|
|
7
|
+
# The MOS 6545 CRTC, as the later PETs drive their screens with: an
|
|
8
|
+
# address register at even addresses selects one of eighteen registers,
|
|
9
|
+
# which the odd addresses write. Of them only the light pen's, R16 and
|
|
10
|
+
# R17, and the cursor address, R14 and R15, read back; every other read
|
|
11
|
+
# finds 0.
|
|
12
|
+
#
|
|
13
|
+
# Its counters run one character a cycle, and compare with the
|
|
14
|
+
# registers as they stand that cycle, so a write lands from the next
|
|
15
|
+
# character on:
|
|
16
|
+
#
|
|
17
|
+
# - The character counter runs from 0 to R0, then a line ends. The
|
|
18
|
+
# display shows from character 0 until the counter equals R1, and
|
|
19
|
+
# horizontal sync starts where it equals R2. A compare the counter has
|
|
20
|
+
# already passed doesn't happen that line: the display then runs on
|
|
21
|
+
# to the line's end.
|
|
22
|
+
# - The line counter (RA, five bits) counts a character row's lines. A
|
|
23
|
+
# row ends after the line where it equals R9; past R9 it runs on to
|
|
24
|
+
# 31 and wraps.
|
|
25
|
+
# - The row counter (seven bits) counts rows to R4. R5 more lines of
|
|
26
|
+
# vertical adjust follow, then the frame starts again. The display
|
|
27
|
+
# stops at the start of the row the counter equals R6, and comes back
|
|
28
|
+
# at the next frame; vertical sync starts with row R7 and lasts
|
|
29
|
+
# VSYNC_LINES lines.
|
|
30
|
+
# - The memory address (MA, fourteen bits) starts each frame at
|
|
31
|
+
# R12:R13 and counts one a character. Each line starts again from its
|
|
32
|
+
# row's start, which the last line of a row moves on to the address
|
|
33
|
+
# where the counter equals R1.
|
|
34
|
+
#
|
|
35
|
+
# Each displayed character goes to the output's #character with MA, RA
|
|
36
|
+
# and the characters since horizontal sync last started; the output
|
|
37
|
+
# also hears of each #hsync as it starts, each #line_end, the
|
|
38
|
+
# #rows_end where the display stops for the rest of the frame, and each
|
|
39
|
+
# #frame_start.
|
|
40
|
+
class CRTC
|
|
41
|
+
# The bits each register keeps.
|
|
42
|
+
MASKS = [0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xff, 0x1f, 0x7f, 0x1f,
|
|
43
|
+
0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xff].freeze
|
|
44
|
+
|
|
45
|
+
VSYNC_LINES = 16
|
|
46
|
+
|
|
47
|
+
attr_reader :registers, :column, :line, :row, :address
|
|
48
|
+
|
|
49
|
+
# The characters since horizontal sync last started.
|
|
50
|
+
attr_reader :since_hsync
|
|
51
|
+
|
|
52
|
+
# +registers+ is the register file, which the output may read too.
|
|
53
|
+
def initialize(output, registers: Array.new(MASKS.length, 0))
|
|
54
|
+
@output = output
|
|
55
|
+
@registers = registers
|
|
56
|
+
reset!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The RES pin stops the counters at the start of a frame. The
|
|
60
|
+
# registers keep their values.
|
|
61
|
+
def reset!
|
|
62
|
+
@selected = 0
|
|
63
|
+
@column = 0
|
|
64
|
+
@since_hsync = 0
|
|
65
|
+
@vsync_lines = 0
|
|
66
|
+
start_frame
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether vertical sync is on.
|
|
70
|
+
def vsync? = @vsync_lines.positive?
|
|
71
|
+
|
|
72
|
+
# Whether the display shows the character the counters are at.
|
|
73
|
+
def display? = @row_display && @column_display
|
|
74
|
+
|
|
75
|
+
def peek(addr)
|
|
76
|
+
return 0 if addr.nobits?(0x01) || @selected < 14 || @selected >= MASKS.length
|
|
77
|
+
|
|
78
|
+
@registers[@selected]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def poke(addr, value)
|
|
82
|
+
if addr.nobits?(0x01)
|
|
83
|
+
@selected = value & 0x1f
|
|
84
|
+
elsif @selected < MASKS.length
|
|
85
|
+
@registers[@selected] = value & MASKS[@selected]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def cycle!
|
|
90
|
+
regs = @registers
|
|
91
|
+
column = @column
|
|
92
|
+
if column == regs[1]
|
|
93
|
+
@column_display = false
|
|
94
|
+
@row_start = @address if @line == regs[9]
|
|
95
|
+
end
|
|
96
|
+
@output.character(@address, @line, @since_hsync) if @column_display && @row_display
|
|
97
|
+
if column == regs[2]
|
|
98
|
+
@since_hsync = 1
|
|
99
|
+
@output.hsync
|
|
100
|
+
else
|
|
101
|
+
@since_hsync += 1
|
|
102
|
+
end
|
|
103
|
+
@address = (@address + 1) & 0x3fff
|
|
104
|
+
if column == regs[0]
|
|
105
|
+
@column = 0
|
|
106
|
+
end_line
|
|
107
|
+
else
|
|
108
|
+
@column = (column + 1) & 0xff
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def end_line
|
|
115
|
+
@output.line_end
|
|
116
|
+
@vsync_lines -= 1 if @vsync_lines.positive?
|
|
117
|
+
@column_display = true
|
|
118
|
+
if @adjusting
|
|
119
|
+
@line += 1
|
|
120
|
+
@line >= @registers[5] ? start_frame : @address = @row_start
|
|
121
|
+
elsif @line == @registers[9]
|
|
122
|
+
end_row
|
|
123
|
+
else
|
|
124
|
+
@line = (@line + 1) & 0x1f
|
|
125
|
+
@address = @row_start
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def end_row
|
|
130
|
+
@line = 0
|
|
131
|
+
if @row == @registers[4]
|
|
132
|
+
return start_frame if @registers[5].zero?
|
|
133
|
+
|
|
134
|
+
@adjusting = true
|
|
135
|
+
else
|
|
136
|
+
@row = (@row + 1) & 0x7f
|
|
137
|
+
start_row
|
|
138
|
+
end
|
|
139
|
+
@address = @row_start
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def start_frame
|
|
143
|
+
@line = 0
|
|
144
|
+
@row = 0
|
|
145
|
+
@adjusting = false
|
|
146
|
+
@row_display = true
|
|
147
|
+
@column_display = true
|
|
148
|
+
@row_start = ((@registers[12] << 8) | @registers[13]) & 0x3fff
|
|
149
|
+
@address = @row_start
|
|
150
|
+
start_row
|
|
151
|
+
@output.frame_start
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def start_row
|
|
155
|
+
if @row == @registers[6] && @row_display
|
|
156
|
+
@row_display = false
|
|
157
|
+
@output.rows_end
|
|
158
|
+
end
|
|
159
|
+
@vsync_lines = VSYNC_LINES if @row == @registers[7]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class PIA
|
|
6
|
+
# One side's control register, CRA or CRB, its two control lines, CA1
|
|
7
|
+
# and CA2 or CB1 and CB2, and their interrupt flags.
|
|
8
|
+
#
|
|
9
|
+
# bit 0 C1 interrupt enable
|
|
10
|
+
# bit 1 C1 active edge, rising when set
|
|
11
|
+
# bit 2 the port (1) or its direction register (0) at the same address
|
|
12
|
+
# bits 5-3 C2:
|
|
13
|
+
# 0e0 input, e the interrupt enable, falling edge
|
|
14
|
+
# 1e0 input, rising edge
|
|
15
|
+
# 100 handshake output
|
|
16
|
+
# 101 pulse output
|
|
17
|
+
# 110 low output
|
|
18
|
+
# 111 high output
|
|
19
|
+
# bit 6 C2 flag, read only, held clear while C2 is an output
|
|
20
|
+
# bit 7 C1 flag, read only
|
|
21
|
+
#
|
|
22
|
+
# A flag is set by its line's active edge whether or not its interrupt
|
|
23
|
+
# is enabled, and both are cleared by a read of the port. In handshake
|
|
24
|
+
# mode C2 goes low on the strobing access and back high on the next
|
|
25
|
+
# active C1 edge; in pulse mode it goes low for the cycle after it.
|
|
26
|
+
# Side A strobes on a read of the port, side B on a write.
|
|
27
|
+
class ControlLines
|
|
28
|
+
attr_reader :c1_high, :c2_high
|
|
29
|
+
|
|
30
|
+
# The level driven on C2, high while it is an input.
|
|
31
|
+
attr_reader :c2_output
|
|
32
|
+
|
|
33
|
+
def initialize(strobe_on_read:)
|
|
34
|
+
@strobe_on_read = strobe_on_read
|
|
35
|
+
@c1_high = @c2_high = true
|
|
36
|
+
reset!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Every bit clear: C2 an input, both interrupts off and the
|
|
40
|
+
# direction register selected. The input levels carry over.
|
|
41
|
+
def reset!
|
|
42
|
+
@control = 0
|
|
43
|
+
@c1_flag = @c2_flag = false
|
|
44
|
+
@handshake_low = false
|
|
45
|
+
@pulse = 0
|
|
46
|
+
drive
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The control register as read, the flags in bits 7 and 6.
|
|
50
|
+
def register
|
|
51
|
+
value = @control
|
|
52
|
+
value |= 0x80 if @c1_flag
|
|
53
|
+
value |= 0x40 if @c2_flag
|
|
54
|
+
value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Writes bits 0-5; the flags can't be written. A new C2 mode starts
|
|
58
|
+
# with any handshake or pulse over.
|
|
59
|
+
def register=(value)
|
|
60
|
+
mode = c2_mode
|
|
61
|
+
@control = value & 0x3f
|
|
62
|
+
@c2_flag = false if c2_output?
|
|
63
|
+
unless c2_mode == mode
|
|
64
|
+
@handshake_low = false
|
|
65
|
+
@pulse = 0
|
|
66
|
+
end
|
|
67
|
+
drive
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def port_selected? = @control.anybits?(0x04)
|
|
71
|
+
|
|
72
|
+
def irq? = (@c1_flag && @control.anybits?(0x01)) || (@c2_flag && @control & 0x28 == 0x08)
|
|
73
|
+
|
|
74
|
+
# Only the pulse output needs the clock.
|
|
75
|
+
def pulsing? = @pulse.positive?
|
|
76
|
+
|
|
77
|
+
def cycle!
|
|
78
|
+
@pulse -= 1
|
|
79
|
+
drive if @pulse.zero?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# A new C1 level. An active edge sets the flag and ends a handshake.
|
|
83
|
+
def c1=(high)
|
|
84
|
+
return if high == @c1_high
|
|
85
|
+
|
|
86
|
+
@c1_high = high
|
|
87
|
+
return unless high == @control.anybits?(0x02)
|
|
88
|
+
|
|
89
|
+
@c1_flag = true
|
|
90
|
+
@handshake_low = false
|
|
91
|
+
drive
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# A new C2 level. An active edge sets the flag while C2 is an input.
|
|
95
|
+
def c2=(high)
|
|
96
|
+
return if high == @c2_high
|
|
97
|
+
|
|
98
|
+
@c2_high = high
|
|
99
|
+
@c2_flag = true if !c2_output? && high == @control.anybits?(0x10)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A read of the port, which clears both flags.
|
|
103
|
+
def port_read!
|
|
104
|
+
@c1_flag = @c2_flag = false
|
|
105
|
+
strobe if @strobe_on_read
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def port_written!
|
|
109
|
+
strobe unless @strobe_on_read
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def c2_output? = @control.anybits?(0x20)
|
|
115
|
+
|
|
116
|
+
def c2_mode = c2_output? ? @control & 0x38 : 0
|
|
117
|
+
|
|
118
|
+
def strobe
|
|
119
|
+
case @control & 0x38
|
|
120
|
+
when 0x20 then @handshake_low = true
|
|
121
|
+
when 0x28 then @pulse = 2
|
|
122
|
+
else return
|
|
123
|
+
end
|
|
124
|
+
drive
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def drive
|
|
128
|
+
@c2_output = case @control & 0x38
|
|
129
|
+
when 0x20 then !@handshake_low
|
|
130
|
+
when 0x28 then @pulse.zero?
|
|
131
|
+
when 0x30 then false
|
|
132
|
+
else true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class PIA
|
|
6
|
+
class ControlLines
|
|
7
|
+
# A side's control register, its lines' levels, its flags and the
|
|
8
|
+
# handshake or pulse under way, under `prefix`. The level driven on
|
|
9
|
+
# C2 follows from them on load.
|
|
10
|
+
module KeyedState
|
|
11
|
+
def save_fields(out, prefix)
|
|
12
|
+
out.int("#{prefix}control", @control).bool("#{prefix}c1_high", @c1_high).bool("#{prefix}c2_high", @c2_high)
|
|
13
|
+
out.bool("#{prefix}c1_flag", @c1_flag).bool("#{prefix}c2_flag", @c2_flag)
|
|
14
|
+
out.bool("#{prefix}handshake_low", @handshake_low).int("#{prefix}pulse", @pulse)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load_fields(input, prefix)
|
|
18
|
+
@control = input.masked("#{prefix}control", @control, 0x3f)
|
|
19
|
+
@c1_high = input.bool("#{prefix}c1_high", @c1_high)
|
|
20
|
+
@c2_high = input.bool("#{prefix}c2_high", @c2_high)
|
|
21
|
+
@c1_flag = input.bool("#{prefix}c1_flag", @c1_flag)
|
|
22
|
+
@c2_flag = input.bool("#{prefix}c2_flag", @c2_flag)
|
|
23
|
+
@handshake_low = input.bool("#{prefix}handshake_low", @handshake_low)
|
|
24
|
+
@pulse = input.masked("#{prefix}pulse", @pulse, 0x03)
|
|
25
|
+
drive
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
include KeyedState
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The PIA's fields in a keyed snapshot module (Snapshot::Keyed): the
|
|
33
|
+
# output and direction registers, and each side's control lines under
|
|
34
|
+
# ca. and cb. The peripheral restores its own state.
|
|
35
|
+
module KeyedState
|
|
36
|
+
def save_fields(out)
|
|
37
|
+
out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
|
|
38
|
+
@ca.save_fields(out, "ca.")
|
|
39
|
+
@cb.save_fields(out, "cb.")
|
|
40
|
+
out
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_fields(input)
|
|
44
|
+
@ora = input.byte("ora", @ora)
|
|
45
|
+
@orb = input.byte("orb", @orb)
|
|
46
|
+
@ddra = input.byte("ddra", @ddra)
|
|
47
|
+
@ddrb = input.byte("ddrb", @ddrb)
|
|
48
|
+
@ca.load_fields(input, "ca.")
|
|
49
|
+
@cb.load_fields(input, "cb.")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
include KeyedState
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/pia/control_lines"
|
|
4
|
+
require "badline/chips/pia/keyed_parts"
|
|
5
|
+
|
|
6
|
+
module Badline
|
|
7
|
+
module Chips
|
|
8
|
+
# The MOS 6520 PIA (Peripheral Interface Adapter), the PET's two: two
|
|
9
|
+
# 8-bit ports, A and B, each with a direction register, a 1 making the
|
|
10
|
+
# pin an output, and a control register for its pair of control lines
|
|
11
|
+
# (ControlLines). Each side has its own IRQ output, IRQA and IRQB. There
|
|
12
|
+
# are no timers.
|
|
13
|
+
#
|
|
14
|
+
# Four registers repeat on two address lines: port A or DDRA, CRA, port
|
|
15
|
+
# B or DDRB and CRB, the port or the direction register picked by bit 2
|
|
16
|
+
# of its control register.
|
|
17
|
+
#
|
|
18
|
+
# Port A reads its pins, whatever drives them. Port B reads its output
|
|
19
|
+
# bits from its output register and its input bits from the pins. An
|
|
20
|
+
# input pin floats high, and a peripheral pulls lines low through
|
|
21
|
+
# read_a and read_b, as a VIA's does. The control lines take levels
|
|
22
|
+
# from outside through ca1= and friends.
|
|
23
|
+
class PIA
|
|
24
|
+
attr_reader :peripheral
|
|
25
|
+
|
|
26
|
+
def initialize(peripheral: nil)
|
|
27
|
+
@peripheral = peripheral
|
|
28
|
+
@ca = ControlLines.new(strobe_on_read: true)
|
|
29
|
+
@cb = ControlLines.new(strobe_on_read: false)
|
|
30
|
+
reset!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The RES line clears every register, which leaves every line an
|
|
34
|
+
# input and releases both IRQs.
|
|
35
|
+
def reset!
|
|
36
|
+
@ora = @orb = @ddra = @ddrb = 0x00
|
|
37
|
+
@ca.reset!
|
|
38
|
+
@cb.reset!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def irqa? = @ca.irq?
|
|
42
|
+
|
|
43
|
+
def irqb? = @cb.irq?
|
|
44
|
+
|
|
45
|
+
def irq? = @ca.irq? || @cb.irq?
|
|
46
|
+
|
|
47
|
+
def cycle!
|
|
48
|
+
@ca.cycle! if @ca.pulsing?
|
|
49
|
+
@cb.cycle! if @cb.pulsing?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cra = @ca.register
|
|
53
|
+
|
|
54
|
+
def crb = @cb.register
|
|
55
|
+
|
|
56
|
+
# Port A as driven by ORA and DDRA, input lines floating high.
|
|
57
|
+
def port_a_output = driven_lines(@ora, @ddra)
|
|
58
|
+
|
|
59
|
+
def port_b_output = driven_lines(@orb, @ddrb)
|
|
60
|
+
|
|
61
|
+
def ca1=(high)
|
|
62
|
+
@ca.c1 = high
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ca2=(high)
|
|
66
|
+
@ca.c2 = high
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def cb1=(high)
|
|
70
|
+
@cb.c1 = high
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def cb2=(high)
|
|
74
|
+
@cb.c2 = high
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ca2_output = @ca.c2_output
|
|
78
|
+
|
|
79
|
+
def cb2_output = @cb.c2_output
|
|
80
|
+
|
|
81
|
+
def peek(addr)
|
|
82
|
+
case addr & 0x03
|
|
83
|
+
when 0x00 then @ca.port_selected? ? read_port_a : @ddra
|
|
84
|
+
when 0x01 then @ca.register
|
|
85
|
+
when 0x02 then @cb.port_selected? ? read_port_b : @ddrb
|
|
86
|
+
else @cb.register
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def poke(addr, value)
|
|
91
|
+
case addr & 0x03
|
|
92
|
+
when 0x00 then write_port_a(value)
|
|
93
|
+
when 0x01 then @ca.register = value
|
|
94
|
+
when 0x02 then write_port_b(value)
|
|
95
|
+
else @cb.register = value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def driven_lines(register, direction)
|
|
102
|
+
(register & direction) | (~direction & 0xff)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def read_port_a
|
|
106
|
+
lines = port_a_output
|
|
107
|
+
lines &= @peripheral.read_a(lines) if @peripheral
|
|
108
|
+
@ca.port_read!
|
|
109
|
+
lines
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def read_port_b
|
|
113
|
+
lines = port_b_output
|
|
114
|
+
lines &= @peripheral.read_b(lines) if @peripheral
|
|
115
|
+
@cb.port_read!
|
|
116
|
+
(@orb & @ddrb) | (lines & ~@ddrb & 0xff)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def write_port_a(value)
|
|
120
|
+
if @ca.port_selected?
|
|
121
|
+
@ora = value
|
|
122
|
+
@ca.port_written!
|
|
123
|
+
else
|
|
124
|
+
@ddra = value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def write_port_b(value)
|
|
129
|
+
if @cb.port_selected?
|
|
130
|
+
@orb = value
|
|
131
|
+
@cb.port_written!
|
|
132
|
+
else
|
|
133
|
+
@ddrb = value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -43,10 +43,7 @@ module Badline
|
|
|
43
43
|
# register back at the value it held when the line started.
|
|
44
44
|
def rewind
|
|
45
45
|
@values.length.times { |reg| @values[reg] = @registers[reg] }
|
|
46
|
-
@seed.
|
|
47
|
-
value = @seed[reg]
|
|
48
|
-
@values[reg] = value unless value.nil?
|
|
49
|
-
end
|
|
46
|
+
@seed.each_with_index { |value, reg| @values[reg] = value unless value.nil? }
|
|
50
47
|
@cursor = 0
|
|
51
48
|
@next_x = @entries.empty? ? Float::INFINITY : @entries[0][0]
|
|
52
49
|
end
|
data/lib/badline/core.rb
CHANGED
|
@@ -18,6 +18,8 @@ require "badline/c64/banked_ram"
|
|
|
18
18
|
require "badline/status"
|
|
19
19
|
require "badline/input"
|
|
20
20
|
require "badline/chips"
|
|
21
|
+
require "badline/chips/pia"
|
|
22
|
+
require "badline/chips/crtc"
|
|
21
23
|
require "badline/drives"
|
|
22
24
|
require "badline/c64/bus"
|
|
23
25
|
require "badline/c64/reu"
|
|
@@ -27,6 +29,7 @@ require "badline/snapshot/storage_setup"
|
|
|
27
29
|
require "badline/machine"
|
|
28
30
|
require "badline/c64"
|
|
29
31
|
require "badline/vic20"
|
|
32
|
+
require "badline/pet"
|
|
30
33
|
require "badline/storage"
|
|
31
34
|
require "badline/sid_tune"
|
|
32
35
|
require "badline/c64/cartridge"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
# The IEEE-488 bus between a PET and its drives: eight data lines, DIO1
|
|
6
|
+
# to DIO8, and the control lines EOI, DAV, NRFD, NDAC and ATN, each open
|
|
7
|
+
# collector and active low, so a line is low while anything on the bus
|
|
8
|
+
# pulls it low. The PET is the controller and always on the bus, and
|
|
9
|
+
# reads back every line it drives.
|
|
10
|
+
#
|
|
11
|
+
# No device plugs in yet, so the lines carry the controller's levels
|
|
12
|
+
# alone: nobody answers a handshake, and the KERNAL reports DEVICE NOT
|
|
13
|
+
# PRESENT for every device the traps don't serve.
|
|
14
|
+
class IEEEBus
|
|
15
|
+
# The lines as bits, the data lines in the low byte, DIO1 in bit 0.
|
|
16
|
+
DIO = 0x00ff
|
|
17
|
+
EOI = 0x0100
|
|
18
|
+
DAV = 0x0200
|
|
19
|
+
NRFD = 0x0400
|
|
20
|
+
NDAC = 0x0800
|
|
21
|
+
ATN = 0x1000
|
|
22
|
+
|
|
23
|
+
# The lines low on the bus while the controller pulls the lines in
|
|
24
|
+
# +controller+ low.
|
|
25
|
+
def low_lines(controller) = controller
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -10,7 +10,8 @@ module Badline
|
|
|
10
10
|
# - the eight data lines join the two port As;
|
|
11
11
|
# - the drive's STATUS0 and STATUS1 (PC0-1) reach the computer's PB0-1;
|
|
12
12
|
# - the drive's ACK (PC3) reaches the computer's PC7;
|
|
13
|
-
# - the computer's DAV (PC6) reaches the drive's PC7
|
|
13
|
+
# - the computer's DAV (PC6) reaches the drive's PC7;
|
|
14
|
+
# - the drive's DEV (PC2) picks the interface's address (Model1551).
|
|
14
15
|
#
|
|
15
16
|
# The drive pulls every line up, so a line neither side drives low
|
|
16
17
|
# reads 1. The computer's other port B and C pins read 0.
|
|
@@ -23,13 +24,14 @@ module Badline
|
|
|
23
24
|
# wakes a drive asleep in its idle loop (atn_moved?).
|
|
24
25
|
class Cable
|
|
25
26
|
STATUS = 0x03
|
|
27
|
+
DEV = 0x04
|
|
26
28
|
ACK = 0x08
|
|
27
29
|
DAV = 0x40
|
|
28
30
|
|
|
29
31
|
attr_reader :host, :drive
|
|
30
32
|
|
|
31
33
|
# Plugs the cable into +host+, the computer's Chips::TIA, and
|
|
32
|
-
# +drive+, the drive's (Model1551::TIA
|
|
34
|
+
# +drive+, the drive's (Model1551::TIA), for +unit+, the drive.
|
|
33
35
|
def initialize(host, drive, unit)
|
|
34
36
|
@host = host
|
|
35
37
|
@drive = drive
|
|
@@ -93,6 +95,7 @@ module Badline
|
|
|
93
95
|
drive_inputs = drive.inputs
|
|
94
96
|
drive_inputs[0] = @data & drive_data
|
|
95
97
|
drive_inputs[2] = (drive_inputs[2] & 0x7f) | (@dav << 1)
|
|
98
|
+
@unit.dev_line(drive_lines.anybits?(DEV))
|
|
96
99
|
return if @moved || (host_data == @data && (host_lines & DAV) == @dav)
|
|
97
100
|
|
|
98
101
|
@moved = true
|