badline 0.15.0 → 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 +102 -0
- data/CLAUDE.md +15 -7
- data/README.md +142 -16
- data/doc/media.md +19 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +274 -31
- data/doc/snapshots.md +14 -0
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/machine_player.rb +2 -14
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- 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/tia.rb +16 -0
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/datasette/keyed_state.rb +8 -3
- data/lib/badline/drives/engine/core.rb +6 -14
- data/lib/badline/drives/engine/idle.rb +42 -4
- data/lib/badline/drives/engine/idle_cpu.rb +18 -0
- data/lib/badline/drives/engine/serial_connection.rb +25 -0
- data/lib/badline/drives/engine/sleep.rb +1 -0
- data/lib/badline/drives/engine.rb +1 -0
- data/lib/badline/drives/ieee_bus.rb +28 -0
- data/lib/badline/drives/model1541/byte_ready.rb +19 -3
- data/lib/badline/drives/model1541/mechanism.rb +10 -1
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551/bus.rb +138 -0
- data/lib/badline/drives/model1551/cable.rb +107 -0
- data/lib/badline/drives/model1551/cpu.rb +14 -0
- data/lib/badline/drives/model1551/keyed_state.rb +65 -0
- data/lib/badline/drives/model1551/port.rb +74 -0
- data/lib/badline/drives/model1551/tia.rb +71 -0
- data/lib/badline/drives/model1551.rb +192 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/boot.rb +20 -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 +15 -6
- data/lib/badline/frontend/menu/menu_media.rb +96 -19
- data/lib/badline/frontend/menu/menu_pages.rb +25 -7
- 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/player/player_controller.rb +11 -4
- 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 +37 -11
- 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 +7 -5
- 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 +23 -5
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +28 -4
- data/lib/badline/options/validation.rb +117 -14
- data/lib/badline/options.rb +65 -21
- 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/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +181 -45
- data/lib/badline/plus4/keyed_modules.rb +22 -5
- data/lib/badline/plus4/keyed_parts.rb +33 -4
- data/lib/badline/plus4/keyed_state.rb +10 -5
- data/lib/badline/plus4/ram_expansion.rb +145 -0
- data/lib/badline/plus4/sid_card.rb +95 -0
- data/lib/badline/plus4/sound_mix.rb +51 -0
- data/lib/badline/plus4/tcbm_drive.rb +134 -0
- data/lib/badline/plus4/user_port.rb +11 -2
- data/lib/badline/plus4.rb +104 -22
- data/lib/badline/roms/README +146 -0
- data/lib/badline/roms/dos1551.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/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 +21 -6
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- data/lib/badline/snapshot/plus4_setup.rb +52 -10
- 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/g64_image.rb +1 -1
- 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 +69 -1
|
@@ -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
|
data/lib/badline/chips/tia.rb
CHANGED
|
@@ -9,20 +9,28 @@ module Badline
|
|
|
9
9
|
#
|
|
10
10
|
# A pin read as an output returns what the port drives, and one read as
|
|
11
11
|
# an input returns what the other side drives, #inputs, per port.
|
|
12
|
+
#
|
|
13
|
+
# A cable plugged in (Drives::Model1551::Cable) hears every write and
|
|
14
|
+
# reset, which can move the pins it carries.
|
|
12
15
|
class TIA
|
|
13
16
|
attr_reader :ports, :directions
|
|
14
17
|
|
|
15
18
|
# What the other side drives onto each port's pins.
|
|
16
19
|
attr_reader :inputs
|
|
17
20
|
|
|
21
|
+
# The cable to the other side, or nil.
|
|
22
|
+
attr_accessor :cable
|
|
23
|
+
|
|
18
24
|
def initialize
|
|
19
25
|
@inputs = [0, 0, 0]
|
|
26
|
+
@cable = nil
|
|
20
27
|
reset!
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def reset!
|
|
24
31
|
@ports = [0, 0, 0]
|
|
25
32
|
@directions = [0, 0, 0]
|
|
33
|
+
@cable&.moved
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def peek(addr)
|
|
@@ -43,6 +51,14 @@ module Badline
|
|
|
43
51
|
else
|
|
44
52
|
@ports[register] = value
|
|
45
53
|
end
|
|
54
|
+
@cable&.moved
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The levels on +port+'s pins as the chip leaves them: an output's
|
|
58
|
+
# level, and 1 for an input, which the chip doesn't drive.
|
|
59
|
+
def output(port)
|
|
60
|
+
direction = @directions[port]
|
|
61
|
+
(@ports[port] & direction) | (~direction & 0xff)
|
|
46
62
|
end
|
|
47
63
|
|
|
48
64
|
# The ports, their directions and what the other side drives, in a
|
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"
|
|
@@ -4,15 +4,17 @@ module Badline
|
|
|
4
4
|
module Drives
|
|
5
5
|
class Datasette
|
|
6
6
|
# The datasette's fields in a keyed snapshot module (Snapshot::Keyed,
|
|
7
|
-
# BL-TAPE): the keys, the motor, the countdown to the next pulse,
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# BL-TAPE): the keys, the motor, the countdown to the next pulse,
|
|
8
|
+
# whether it records, the cycles since the write line's last edge and
|
|
9
|
+
# that line's level, and a tape in it under tape.*: its path, its
|
|
10
|
+
# bytes and how far it has played. A tape that isn't in leaves tape.* out. A datasette that
|
|
10
11
|
# times the read line's level adds when in the countdown the line
|
|
11
12
|
# rises, and whether a recording's low half wave is on the tape. The
|
|
12
13
|
# sense and flag handlers don't fire on load.
|
|
13
14
|
module KeyedState
|
|
14
15
|
def save_fields(out)
|
|
15
16
|
out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
|
|
17
|
+
out.bool("recording", @recording).int("since", @since).bool("write_high", @write_high)
|
|
16
18
|
out.int("rise_at", @rise_at).bool("low_recorded", @low_recorded) if @level
|
|
17
19
|
tape = @tape
|
|
18
20
|
return out unless tape
|
|
@@ -26,6 +28,9 @@ module Badline
|
|
|
26
28
|
@playing = input.bool("playing", @playing)
|
|
27
29
|
@motor = input.bool("motor", @motor)
|
|
28
30
|
@countdown = input.int("countdown", @countdown)
|
|
31
|
+
@recording = input.bool("recording", @recording)
|
|
32
|
+
@since = input.int("since", @since)
|
|
33
|
+
@write_high = input.bool("write_high", @write_high)
|
|
29
34
|
@rise_at = input.int("rise_at", @rise_at)
|
|
30
35
|
@low_recorded = input.bool("low_recorded", @low_recorded)
|
|
31
36
|
bytes = input.bytes("tape.bytes", "".b).bytes
|
|
@@ -24,8 +24,11 @@ module Badline
|
|
|
24
24
|
# sleeps through them too (see Orbit).
|
|
25
25
|
#
|
|
26
26
|
# A model includes Core and builds its parts, sets @clock_hz and
|
|
27
|
-
# @idle_loop, and runs one drive cycle in step.
|
|
28
|
-
#
|
|
27
|
+
# @idle_loop, and runs one drive cycle in step. Its @serial_port faces
|
|
28
|
+
# the host: it says whether the host moved the lines the drive
|
|
29
|
+
# listens to (atn_moved?) and takes them for the next host cycle
|
|
30
|
+
# (latch_host), as the serial port does for ATN on the serial bus
|
|
31
|
+
# (SerialConnection) and the TCBM cable does on the 1551. The model
|
|
29
32
|
# answers Idle's and Orbit's questions about its chips, as VIAs does
|
|
30
33
|
# for the 1541 and the 1571.
|
|
31
34
|
module Core
|
|
@@ -33,7 +36,7 @@ module Badline
|
|
|
33
36
|
include Idle
|
|
34
37
|
include Orbit
|
|
35
38
|
|
|
36
|
-
attr_reader :device
|
|
39
|
+
attr_reader :device
|
|
37
40
|
|
|
38
41
|
def cpu
|
|
39
42
|
settle!
|
|
@@ -62,17 +65,6 @@ module Badline
|
|
|
62
65
|
@host_clock_hz = clock_hz
|
|
63
66
|
end
|
|
64
67
|
|
|
65
|
-
# Plugs the drive into a serial bus, leaving the one it was on. A drive
|
|
66
|
-
# starts out on a bus of its own, with nothing else on it.
|
|
67
|
-
def connect(serial_bus)
|
|
68
|
-
settle!
|
|
69
|
-
@serial_bus&.detach(self)
|
|
70
|
-
@serial_bus = serial_bus
|
|
71
|
-
@serial_port.bus = serial_bus
|
|
72
|
-
serial_bus.attach(self)
|
|
73
|
-
hear_atn
|
|
74
|
-
end
|
|
75
|
-
|
|
76
68
|
# Puts a Disk in the drive (Disk.from_d64 makes one from an image).
|
|
77
69
|
# Nil takes the disk out.
|
|
78
70
|
def insert(disk)
|