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
|
@@ -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,74 @@
|
|
|
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
|
+
# The controller pushes the lines it pulls whenever they may have moved
|
|
12
|
+
# (host_lines=), and the bus holds them for the drives, which take them
|
|
13
|
+
# a host cycle late. Each drive on the bus answers the lines it pulls
|
|
14
|
+
# (ieee_low_lines) while ATN is or isn't low, from its own outputs, which
|
|
15
|
+
# hold still while it sleeps. With no drive on the bus nobody answers a
|
|
16
|
+
# handshake, and the KERNAL reports DEVICE NOT PRESENT for every device
|
|
17
|
+
# the traps don't serve.
|
|
18
|
+
class IEEEBus
|
|
19
|
+
# The lines as bits, the data lines in the low byte, DIO1 in bit 0.
|
|
20
|
+
DIO = 0x00ff
|
|
21
|
+
EOI = 0x0100
|
|
22
|
+
DAV = 0x0200
|
|
23
|
+
NRFD = 0x0400
|
|
24
|
+
NDAC = 0x0800
|
|
25
|
+
ATN = 0x1000
|
|
26
|
+
|
|
27
|
+
# The lines the controller last pushed.
|
|
28
|
+
attr_reader :host_lines
|
|
29
|
+
|
|
30
|
+
attr_reader :drives
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@drives = []
|
|
34
|
+
@host_lines = 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The controller pushes the lines it pulls, and the drives look at
|
|
38
|
+
# them again.
|
|
39
|
+
def host_lines=(lines)
|
|
40
|
+
@host_lines = lines
|
|
41
|
+
drives = @drives
|
|
42
|
+
i = 0
|
|
43
|
+
while i < drives.length
|
|
44
|
+
drives[i].host_written!
|
|
45
|
+
i += 1
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def attach(drive)
|
|
50
|
+
@drives << drive unless @drives.include?(drive)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def detach(drive)
|
|
54
|
+
@drives.delete(drive)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The lines low on the bus while the controller pulls the lines in
|
|
58
|
+
# +controller+ low.
|
|
59
|
+
def low_lines(controller)
|
|
60
|
+
low = controller
|
|
61
|
+
drives = @drives
|
|
62
|
+
return low if drives.empty?
|
|
63
|
+
|
|
64
|
+
atn = controller.anybits?(ATN)
|
|
65
|
+
i = 0
|
|
66
|
+
while i < drives.length
|
|
67
|
+
low |= drives[i].ieee_low_lines(atn)
|
|
68
|
+
i += 1
|
|
69
|
+
end
|
|
70
|
+
low
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
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
|
|
@@ -4,8 +4,7 @@ module Badline
|
|
|
4
4
|
module Drives
|
|
5
5
|
class Model1551
|
|
6
6
|
# The drive's 6523 TIA at $4000-$7FFF, its eight registers mirrored
|
|
7
|
-
# (zimmers.net's 1551 memory map, schematic 251860)
|
|
8
|
-
# with what the drive's side wires to it:
|
|
7
|
+
# (zimmers.net's 1551 memory map, schematic 251860):
|
|
9
8
|
#
|
|
10
9
|
# - Port A carries the cable's eight data lines, pulled up.
|
|
11
10
|
# - Port B reads the GCR byte the gate array latched at BYTE READY
|
|
@@ -19,72 +18,52 @@ module Badline
|
|
|
19
18
|
# pulled up, so an input nothing drives reads 1.
|
|
20
19
|
#
|
|
21
20
|
# Any access to the TIA clears BYTE LATCHED.
|
|
22
|
-
class TIA
|
|
21
|
+
class TIA < Chips::TIA
|
|
23
22
|
DAV = 0x80
|
|
24
23
|
SYNC = 0x40
|
|
25
24
|
JUMPER = 0x20
|
|
26
25
|
MODE = 0x10
|
|
27
26
|
|
|
28
|
-
# The chip, whose inputs the drive's side sets before each read.
|
|
29
|
-
attr_reader :chip
|
|
30
|
-
|
|
31
27
|
def initialize(mechanism, device:)
|
|
32
28
|
@mechanism = mechanism
|
|
33
29
|
@jumper = device == 9 ? JUMPER : 0
|
|
34
|
-
@chip = Chips::TIA.new
|
|
35
|
-
inputs = @chip.inputs
|
|
36
|
-
inputs[0] = 0xff
|
|
37
|
-
inputs[1] = 0xff
|
|
38
|
-
inputs[2] = 0xff
|
|
39
30
|
mechanism.tia = self
|
|
40
|
-
|
|
31
|
+
super()
|
|
32
|
+
@inputs[0] = 0xff
|
|
33
|
+
@inputs[1] = 0xff
|
|
34
|
+
@inputs[2] = 0xff
|
|
41
35
|
end
|
|
42
36
|
|
|
43
|
-
def ports = @chip.ports
|
|
44
|
-
|
|
45
|
-
def directions = @chip.directions
|
|
46
|
-
|
|
47
|
-
def inputs = @chip.inputs
|
|
48
|
-
|
|
49
37
|
def reset!
|
|
50
|
-
|
|
38
|
+
super
|
|
51
39
|
mode_written
|
|
52
40
|
end
|
|
53
41
|
|
|
54
42
|
def peek(addr)
|
|
55
43
|
mechanism = @mechanism
|
|
56
44
|
mechanism.release_byte_latch
|
|
57
|
-
chip = @chip
|
|
58
|
-
inputs = chip.inputs
|
|
59
45
|
case addr & 0x07
|
|
60
|
-
when 1 then inputs[1] = mechanism.latched_byte
|
|
61
|
-
when 2 then inputs[2] = (inputs[2] & DAV) | (mechanism.sync? ? 0 : SYNC) | @jumper | 0x1f
|
|
46
|
+
when 1 then @inputs[1] = mechanism.latched_byte
|
|
47
|
+
when 2 then @inputs[2] = (@inputs[2] & DAV) | (mechanism.sync? ? 0 : SYNC) | @jumper | 0x1f
|
|
62
48
|
end
|
|
63
|
-
|
|
49
|
+
super
|
|
64
50
|
end
|
|
65
51
|
|
|
66
52
|
def poke(addr, value)
|
|
67
53
|
@mechanism.release_byte_latch
|
|
68
|
-
|
|
54
|
+
super
|
|
69
55
|
register = addr & 0x07
|
|
70
56
|
mode_written if [2, 5].include?(register)
|
|
71
57
|
end
|
|
72
58
|
|
|
73
59
|
# What port B drives onto the head's write shift register.
|
|
74
|
-
def port_b_output =
|
|
75
|
-
|
|
76
|
-
# The chip's registers, in a keyed snapshot module (BL-D8-TIA).
|
|
77
|
-
def save_fields(out) = @chip.save_fields(out)
|
|
78
|
-
|
|
79
|
-
def load_fields(input)
|
|
80
|
-
@chip.load_fields(input)
|
|
81
|
-
end
|
|
60
|
+
def port_b_output = output(1)
|
|
82
61
|
|
|
83
62
|
private
|
|
84
63
|
|
|
85
64
|
# MODE selects reading while high, as the 1541's CB2 does.
|
|
86
65
|
def mode_written
|
|
87
|
-
@mechanism.cb2_written(
|
|
66
|
+
@mechanism.cb2_written(output(2).anybits?(MODE))
|
|
88
67
|
end
|
|
89
68
|
end
|
|
90
69
|
end
|
|
@@ -26,7 +26,10 @@ module Badline
|
|
|
26
26
|
# The drive comes with its interface for the computer's expansion port,
|
|
27
27
|
# a second 6523 (#host_tia), which the cable joins to the drive's
|
|
28
28
|
# (Cable). The computer puts it at $FEE0 for device 8 and at $FEC0 for
|
|
29
|
-
# device 9
|
|
29
|
+
# device 9, as the drive's DEV line (PC2) says: high for device 8 and
|
|
30
|
+
# low for device 9. The DOS sets DEV from the jumper at reset, and again
|
|
31
|
+
# when a command renumbers the drive. The device number follows each
|
|
32
|
+
# change on the line (#device).
|
|
30
33
|
#
|
|
31
34
|
# A 555 timer pulls IRQ low for about 7 µs every 8.3 ms, its R1 120K,
|
|
32
35
|
# R2 100R and C1 0.1 µF, which runs the DOS's job loop. Nothing
|
|
@@ -101,10 +104,29 @@ module Badline
|
|
|
101
104
|
@irq_timer = IRQ_PERIOD
|
|
102
105
|
init_idle(debug)
|
|
103
106
|
@pass_counter = PASS_COUNTER
|
|
104
|
-
@
|
|
107
|
+
@dev_high = true
|
|
108
|
+
@on_renumber = nil
|
|
109
|
+
@cable = Cable.new(@host_tia, @tia, self)
|
|
105
110
|
@serial_port = @cable
|
|
106
111
|
end
|
|
107
112
|
|
|
113
|
+
# Calls the block with the old device number each time the DEV line
|
|
114
|
+
# moves the drive to the other one.
|
|
115
|
+
def on_renumber(&block)
|
|
116
|
+
@on_renumber = block
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The cable's DEV line, high or not. A change moves the drive to
|
|
120
|
+
# device 8 while it's high, and to device 9 while it's low.
|
|
121
|
+
def dev_line(high)
|
|
122
|
+
return if high == @dev_high
|
|
123
|
+
|
|
124
|
+
@dev_high = high
|
|
125
|
+
old = @device
|
|
126
|
+
@device = high ? 8 : 9
|
|
127
|
+
@on_renumber&.call(old) unless @device == old
|
|
128
|
+
end
|
|
129
|
+
|
|
108
130
|
# Puts in a Disk from the .d64 or .g64 image at +path+, write-protected
|
|
109
131
|
# with `read_only`.
|
|
110
132
|
def insert_image(path, read_only: false)
|
|
@@ -148,8 +170,8 @@ module Badline
|
|
|
148
170
|
end
|
|
149
171
|
|
|
150
172
|
def idle_state
|
|
151
|
-
|
|
152
|
-
[*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *
|
|
173
|
+
tia = @tia
|
|
174
|
+
[*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *tia.ports, *tia.directions, *tia.inputs,
|
|
153
175
|
@port.ddr, @port.data]
|
|
154
176
|
end
|
|
155
177
|
|