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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/CLAUDE.md +8 -7
  4. data/README.md +128 -9
  5. data/doc/media.md +9 -0
  6. data/doc/pinned-behaviour.md +67 -0
  7. data/doc/snapshot-format.md +215 -18
  8. data/doc/snapshots.md +55 -0
  9. data/lib/badline/c64/common.rb +1 -1
  10. data/lib/badline/chips/crtc/keyed_state.rb +46 -0
  11. data/lib/badline/chips/crtc.rb +163 -0
  12. data/lib/badline/chips/pia/control_lines.rb +138 -0
  13. data/lib/badline/chips/pia/keyed_parts.rb +56 -0
  14. data/lib/badline/chips/pia.rb +138 -0
  15. data/lib/badline/chips/vic/register_log.rb +1 -4
  16. data/lib/badline/core.rb +3 -0
  17. data/lib/badline/drives/ieee_bus.rb +74 -0
  18. data/lib/badline/drives/model1551/cable.rb +5 -2
  19. data/lib/badline/drives/model1551/tia.rb +13 -34
  20. data/lib/badline/drives/model1551.rb +26 -4
  21. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  22. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  23. data/lib/badline/drives/model2031/parts.rb +25 -0
  24. data/lib/badline/drives/model2031.rb +92 -0
  25. data/lib/badline/drives.rb +1 -0
  26. data/lib/badline/frontend/app.rb +1 -1
  27. data/lib/badline/frontend/controls.rb +23 -3
  28. data/lib/badline/frontend/gamepads.rb +10 -3
  29. data/lib/badline/frontend/keys.rb +88 -0
  30. data/lib/badline/frontend/media_slots.rb +1 -0
  31. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
  32. data/lib/badline/frontend/menu/menu_media.rb +27 -28
  33. data/lib/badline/frontend/menu/menu_pages.rb +24 -2
  34. data/lib/badline/frontend/menu/menu_ports.rb +22 -1
  35. data/lib/badline/frontend/menu/pause_menu.rb +1 -0
  36. data/lib/badline/frontend/pet_keyboard.rb +124 -0
  37. data/lib/badline/frontend/screens.rb +2 -1
  38. data/lib/badline/frontend/timeline.rb +16 -9
  39. data/lib/badline/frontend.rb +1 -0
  40. data/lib/badline/kernal_trap/dos/writes.rb +3 -1
  41. data/lib/badline/kernal_trap/dos.rb +6 -1
  42. data/lib/badline/kernal_trap/frames.rb +117 -0
  43. data/lib/badline/kernal_trap/ieee.rb +151 -0
  44. data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
  45. data/lib/badline/kernal_trap/keyed_state.rb +4 -4
  46. data/lib/badline/kernal_trap/routine.rb +3 -53
  47. data/lib/badline/kernal_trap/serial.rb +4 -102
  48. data/lib/badline/kernal_trap/steps.rb +64 -0
  49. data/lib/badline/kernal_trap.rb +4 -0
  50. data/lib/badline/machine/keyboard_buffer.rb +8 -0
  51. data/lib/badline/machine/settling.rb +1 -1
  52. data/lib/badline/machine.rb +29 -6
  53. data/lib/badline/media/extensions.rb +5 -2
  54. data/lib/badline/media/pet_media.rb +40 -0
  55. data/lib/badline/media/program.rb +14 -2
  56. data/lib/badline/media/true_drive.rb +19 -6
  57. data/lib/badline/media/vic20_media.rb +2 -2
  58. data/lib/badline/media.rb +41 -17
  59. data/lib/badline/options/family.rb +17 -6
  60. data/lib/badline/options/help.rb +23 -3
  61. data/lib/badline/options/table.rb +22 -4
  62. data/lib/badline/options/validation.rb +84 -5
  63. data/lib/badline/options.rb +47 -16
  64. data/lib/badline/pet/bus/expansion.rb +200 -0
  65. data/lib/badline/pet/bus.rb +222 -0
  66. data/lib/badline/pet/canvas.rb +97 -0
  67. data/lib/badline/pet/cassettes.rb +63 -0
  68. data/lib/badline/pet/characters.rb +32 -0
  69. data/lib/badline/pet/cpu.rb +10 -0
  70. data/lib/badline/pet/crtc_video.rb +155 -0
  71. data/lib/badline/pet/discrete_video.rb +83 -0
  72. data/lib/badline/pet/ieee_drive.rb +76 -0
  73. data/lib/badline/pet/ieee_ports.rb +77 -0
  74. data/lib/badline/pet/ieee_traps.rb +80 -0
  75. data/lib/badline/pet/keyboard_ports.rb +74 -0
  76. data/lib/badline/pet/keyed_modules.rb +88 -0
  77. data/lib/badline/pet/keyed_parts.rb +127 -0
  78. data/lib/badline/pet/keyed_state.rb +42 -0
  79. data/lib/badline/pet/model.rb +163 -0
  80. data/lib/badline/pet/rom_sets.rb +89 -0
  81. data/lib/badline/pet/sound.rb +128 -0
  82. data/lib/badline/pet/via.rb +10 -0
  83. data/lib/badline/pet/via_ports.rb +19 -0
  84. data/lib/badline/pet.rb +244 -0
  85. data/lib/badline/plus4/keyed_state.rb +5 -3
  86. data/lib/badline/plus4/tcbm_drive.rb +14 -0
  87. data/lib/badline/plus4.rb +1 -1
  88. data/lib/badline/roms/README +169 -0
  89. data/lib/badline/roms/dos2031.rom +0 -0
  90. data/lib/badline/roms/pet/basic1.rom +0 -0
  91. data/lib/badline/roms/pet/basic2.rom +0 -0
  92. data/lib/badline/roms/pet/basic4.rom +0 -0
  93. data/lib/badline/roms/pet/characters-din.rom +0 -0
  94. data/lib/badline/roms/pet/characters1.rom +0 -0
  95. data/lib/badline/roms/pet/characters2.rom +0 -0
  96. data/lib/badline/roms/pet/editor1.rom +0 -0
  97. data/lib/badline/roms/pet/editor2-b.rom +0 -0
  98. data/lib/badline/roms/pet/editor2-n.rom +0 -0
  99. data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
  100. data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
  101. data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
  102. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  103. data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
  104. data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
  105. data/lib/badline/roms/pet/editor4-b.rom +0 -0
  106. data/lib/badline/roms/pet/editor4-n.rom +0 -0
  107. data/lib/badline/roms/pet/kernal1.rom +0 -0
  108. data/lib/badline/roms/pet/kernal2.rom +0 -0
  109. data/lib/badline/roms/pet/kernal4.rom +0 -0
  110. data/lib/badline/snapshot/container.rb +6 -1
  111. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  112. data/lib/badline/snapshot/field_doc.rb +17 -3
  113. data/lib/badline/snapshot/image.rb +28 -10
  114. data/lib/badline/snapshot/pet_setup.rb +88 -0
  115. data/lib/badline/snapshot/storage_setup.rb +7 -1
  116. data/lib/badline/snapshot/vice/pet.rb +117 -0
  117. data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
  118. data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
  119. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  120. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  121. data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
  122. data/lib/badline/snapshot/vice/pet_via.rb +164 -0
  123. data/lib/badline/snapshot/vice.rb +1 -0
  124. data/lib/badline/snapshot.rb +9 -5
  125. data/lib/badline/storage/d80_image.rb +48 -0
  126. data/lib/badline/storage/d82_image.rb +21 -0
  127. data/lib/badline/storage/tap.rb +16 -3
  128. data/lib/badline/storage.rb +2 -0
  129. data/lib/badline/support/pet_checkpoint.rb +38 -0
  130. data/lib/badline/timing.rb +5 -4
  131. data/lib/badline/version.rb +1 -1
  132. data/lib/badline/vic20/attachments.rb +7 -2
  133. data/lib/badline/vic20/bus/keyed_state.rb +5 -4
  134. data/lib/badline/vic20/bus.rb +5 -0
  135. data/lib/badline/vic20.rb +1 -1
  136. 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.each_index do |reg|
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#chip), for +unit+, the drive.
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), a Chips::TIA
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
- mode_written
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
- @chip.reset!
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
- chip.peek(addr)
49
+ super
64
50
  end
65
51
 
66
52
  def poke(addr, value)
67
53
  @mechanism.release_byte_latch
68
- @chip.poke(addr, value)
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 = @chip.output(1)
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(@chip.output(2).anybits?(MODE))
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
- @cable = Cable.new(@host_tia, @tia.chip, self)
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
- chip = @tia.chip
152
- [*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *chip.ports, *chip.directions, *chip.inputs,
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