badline 0.16.0 → 0.17.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.
Files changed (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +130 -17
  5. data/doc/media.md +14 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +379 -70
  8. data/doc/snapshots.md +185 -10
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/ieee_bus.rb +50 -4
  46. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  47. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  48. data/lib/badline/drives/model2031/parts.rb +25 -0
  49. data/lib/badline/drives/model2031.rb +92 -0
  50. data/lib/badline/drives/model8050/disk.rb +163 -0
  51. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  52. data/lib/badline/drives/model8050/idle.rb +177 -0
  53. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  54. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  55. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  56. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  57. data/lib/badline/drives/model8050/parts.rb +37 -0
  58. data/lib/badline/drives/model8050.rb +243 -0
  59. data/lib/badline/frontend/app.rb +10 -3
  60. data/lib/badline/frontend/boot.rb +1 -1
  61. data/lib/badline/frontend/controls.rb +51 -15
  62. data/lib/badline/frontend/gamepads.rb +3 -3
  63. data/lib/badline/frontend/keys.rb +61 -2
  64. data/lib/badline/frontend/media_slots.rb +1 -1
  65. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  66. data/lib/badline/frontend/menu/menu_pages.rb +29 -6
  67. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  68. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  69. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  70. data/lib/badline/frontend/timeline.rb +19 -10
  71. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  72. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  73. data/lib/badline/kernal_trap/dos.rb +18 -5
  74. data/lib/badline/kernal_trap/ieee.rb +7 -2
  75. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  76. data/lib/badline/machine/ieee_drives.rb +194 -0
  77. data/lib/badline/machine/settling.rb +4 -0
  78. data/lib/badline/machine.rb +26 -8
  79. data/lib/badline/media/cbm2_media.rb +29 -0
  80. data/lib/badline/media/pet_media.rb +5 -5
  81. data/lib/badline/media/program.rb +6 -3
  82. data/lib/badline/media/true_drive.rb +50 -8
  83. data/lib/badline/media.rb +21 -8
  84. data/lib/badline/options/family.rb +15 -3
  85. data/lib/badline/options/help.rb +24 -6
  86. data/lib/badline/options/table.rb +25 -5
  87. data/lib/badline/options/validation.rb +47 -7
  88. data/lib/badline/options.rb +48 -15
  89. data/lib/badline/pet/bus/expansion.rb +99 -5
  90. data/lib/badline/pet/bus.rb +30 -12
  91. data/lib/badline/pet/cassettes.rb +22 -1
  92. data/lib/badline/pet/characters.rb +8 -2
  93. data/lib/badline/pet/crtc_video.rb +20 -7
  94. data/lib/badline/pet/ieee_drive.rb +17 -0
  95. data/lib/badline/pet/ieee_ports.rb +19 -1
  96. data/lib/badline/pet/ieee_traps.rb +10 -5
  97. data/lib/badline/pet/keyed_modules.rb +4 -1
  98. data/lib/badline/pet/keyed_parts.rb +10 -6
  99. data/lib/badline/pet/model.rb +56 -6
  100. data/lib/badline/pet/rom_sets.rb +30 -6
  101. data/lib/badline/pet.rb +45 -16
  102. data/lib/badline/plus4/acia.rb +4 -298
  103. data/lib/badline/plus4/keyed_parts.rb +0 -44
  104. data/lib/badline/plus4.rb +1 -0
  105. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  106. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  107. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  108. data/lib/badline/roms/README +144 -1
  109. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  110. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  111. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  112. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  113. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  114. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  115. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  116. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  117. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  118. data/lib/badline/roms/dos2031.rom +0 -0
  119. data/lib/badline/roms/pet/characters-din.rom +0 -0
  120. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  121. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  122. data/lib/badline/snapshot/container.rb +9 -1
  123. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  124. data/lib/badline/snapshot/field_doc.rb +39 -3
  125. data/lib/badline/snapshot/image.rb +79 -15
  126. data/lib/badline/snapshot/pet_setup.rb +30 -13
  127. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  128. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  129. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  130. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  131. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  132. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  133. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  134. data/lib/badline/snapshot/vice/cias.rb +13 -9
  135. data/lib/badline/snapshot/vice/p500.rb +96 -0
  136. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  137. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  138. data/lib/badline/snapshot/vice/pet.rb +121 -0
  139. data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
  140. data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
  141. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  142. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  143. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  144. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  145. data/lib/badline/snapshot/vice/pet_restore.rb +214 -0
  146. data/lib/badline/snapshot/vice/pet_via.rb +165 -0
  147. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  148. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  149. data/lib/badline/snapshot/vice.rb +4 -0
  150. data/lib/badline/snapshot.rb +11 -3
  151. data/lib/badline/storage/crt_file.rb +7 -1
  152. data/lib/badline/storage/d64_image.rb +7 -0
  153. data/lib/badline/storage/d80_image.rb +17 -1
  154. data/lib/badline/storage/d81_image.rb +14 -0
  155. data/lib/badline/storage/disk_image/bam.rb +18 -3
  156. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  157. data/lib/badline/storage/disk_image/writing.rb +35 -0
  158. data/lib/badline/storage/disk_image.rb +2 -0
  159. data/lib/badline/storage.rb +1 -0
  160. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  161. data/lib/badline/support/p500_checkpoint.rb +35 -0
  162. data/lib/badline/version.rb +1 -1
  163. data/lib/badline.rb +1 -0
  164. metadata +87 -2
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # A true 2031 on device 8, as xpet 3.10, xcbm2 and xcbm5x0 save it with true drive
8
+ # emulation: DRIVE8 to DRIVE11 2.0, one a unit, DRIVECPU0 1.3, the
9
+ # drive's 6502 and its RAM, and its VIAs as VIA 2.2 modules,
10
+ # 2031VIA1D0 at $1800 on the IEEE-488 bus and VIA2D0 at $1C00 on
11
+ # the mechanism.
12
+ #
13
+ # DRIVECPU0 holds the clock, the registers, the last opcode, the
14
+ # main CPU's clock, more clocks of the drive's interrupt bookkeeping,
15
+ # then the 2K of RAM from $5C and twelve bytes after it. DRIVE8
16
+ # holds the head's half track at $10, with track 18 at 36 as badline
17
+ # numbers them, the speed zone its VIA set at $24, $47 and $4B, the
18
+ # drive's type as a word at $28, and from $2C the disk's place in
19
+ # its turn and the clock it was last moved on at. The rest of it,
20
+ # the read electronics among it, badline doesn't read. xpet's head
21
+ # rests on a half track two phases on from where badline's stepper
22
+ # puts it (SLIP).
23
+ #
24
+ # Written: the registers and the RAM, the VIAs as the PET's VIA is
25
+ # written with the stepper's phase moved to xpet's, and DRIVE8 as
26
+ # xpet saves an idle 2031 but for the head's half track, the zone,
27
+ # and the disk at the start of its turn as of the snapshot's clock.
28
+ # The interrupt clocks are written as long past. The snapshot is
29
+ # settled with the drive's CPU between instructions too
30
+ # (PET#settle_for_vice!). One taken while the drive is mid-transfer
31
+ # on the bus may stall there in xpet, as xpet's own snapshots taken
32
+ # while it searches for a file do.
33
+ #
34
+ # Read: the registers, the RAM, the VIAs as the PET's VIA is read,
35
+ # and the head's half track, with the motor, the LED and the zone
36
+ # from VIA 2's port B and the stepper's phase as xpet's (SLIP). The
37
+ # disk turns on from where badline's left it, and the disk in the
38
+ # drive stays.
39
+ module Drive2031
40
+ UNITS = %w[DRIVE8 DRIVE9 DRIVE10 DRIVE11].freeze
41
+ DRIVE_MAJOR = 2
42
+ DRIVE_MINOR = 0
43
+ CPU = "DRIVECPU0"
44
+ CPU_MAJOR = 1
45
+ CPU_MINOR = 3
46
+ VIA1 = "2031VIA1D0"
47
+ VIA2 = "VIA2D0"
48
+ NAMES = [*UNITS, CPU, VIA1, VIA2].freeze
49
+ # Where DRIVECPU0's RAM starts, and its length.
50
+ RAM_AT = 0x5c
51
+ RAM_SIZE = 0x800
52
+ TAIL = 12
53
+ HALF_TRACK = 0x10
54
+ ZONES = [0x24, 0x47, 0x4b].freeze
55
+ SIZE = 145
56
+ # The disk's place in its turn, and the clock it was last moved on at.
57
+ TURN = 0x2c
58
+ # DRIVE8 as xpet saves an idle 2031, and a unit with no drive, by
59
+ # the bytes that aren't 0.
60
+ IDLE = { 0x00 => 0x01, 0x01 => 0x01, 0x02 => 0x01, 0x0f => 0x01, 0x10 => 0x25, 0x1a => 0x01, 0x1b => 0xdf,
61
+ 0x1f => 0x54, 0x20 => 0x55, 0x24 => 0x03, 0x28 => 0xef, 0x29 => 0x07, 0x2c => 0x60, 0x2d => 0x41,
62
+ 0x2e => 0x0f, 0x30 => 0x17, 0x31 => 0x51, 0x32 => 0x0f, 0x40 => 0x54, 0x41 => 0x01, 0x42 => 0x54,
63
+ 0x47 => 0x03, 0x4b => 0x03, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12 }.freeze
64
+ NONE = { 0x00 => 0x01, 0x01 => 0x01, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x24, 0x1a => 0x01,
65
+ 0x20 => 0x55, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12, 0x8f => 0x01 }.freeze
66
+ SLIP = 2
67
+ MOTOR = 0x04
68
+ LED = 0x08
69
+
70
+ module_function
71
+
72
+ # Whether the snapshot has a true 2031.
73
+ def present?(container) = !container[CPU].nil? && !container[VIA1].nil?
74
+
75
+ def reads?(section)
76
+ case section.name
77
+ when CPU then section.major == CPU_MAJOR && section.minor == CPU_MINOR
78
+ when VIA1, VIA2 then VIA.reads?(section)
79
+ else section.major == DRIVE_MAJOR && section.minor == DRIVE_MINOR
80
+ end
81
+ end
82
+
83
+ # xpet's modules for the 2031 whose BL-D8 modules `readers` holds,
84
+ # and none without one or for an 8050 or 8250, which they leave
85
+ # out.
86
+ def export(machine, readers)
87
+ drive = readers["BL-D8"]
88
+ return [] if drive.nil? || drive.text("model", "2031") != "2031"
89
+
90
+ via2 = readers.fetch("BL-D8-VIA2")
91
+ mechanism = readers.fetch("BL-D8-MECH")
92
+ zone = ((via2.int("orb", 0) | ~via2.int("ddrb", 0)) >> 5) & 0x03
93
+ idle = filled(IDLE)
94
+ idle[HALF_TRACK] = mechanism.int("half_track", 36)
95
+ ZONES.each { |offset| idle[offset] = zone }
96
+ idle[TURN, 8] = [0, machine.cycles & 0xffff_ffff].pack("VV").bytes
97
+ [FieldWriter.new.bytes(idle).section(UNITS.first, DRIVE_MAJOR, DRIVE_MINOR),
98
+ *UNITS.drop(1).map { |name| unit(NONE, name) },
99
+ cpu(machine, readers.fetch("BL-D8-CPU"), readers.fetch("BL-D8-RAM")),
100
+ stepper(VIA.export(via2, VIA2), mechanism.int("slip", 0)),
101
+ VIA.export(readers.fetch("BL-D8-VIA1"), VIA1)]
102
+ end
103
+
104
+ # VIA 2's port B with the stepper's phase as xpet's head takes it
105
+ # where badline's head is: SLIP on from badline's.
106
+ def stepper(section, slip)
107
+ bytes = section.data.bytes
108
+ bytes[2] = (bytes[2] & 0xfc) | ((bytes[2] - slip + SLIP) & 0x03)
109
+ section.with(data: bytes.pack("C*"))
110
+ end
111
+
112
+ def unit(bytes, name) = FieldWriter.new.bytes(filled(bytes)).section(name, DRIVE_MAJOR, DRIVE_MINOR)
113
+
114
+ def filled(bytes) = Array.new(SIZE) { |offset| bytes.fetch(offset, 0) }
115
+
116
+ def cpu(machine, input, ram)
117
+ bytes = ram.bytes("ram", "".b).bytes
118
+ cpu_section(machine, input, Array.new(RAM_SIZE) { |address| bytes[address] || 0 })
119
+ end
120
+
121
+ # The drive's clock, at its 1 MHz, for the machine's at its own.
122
+ def drive_clock(machine) = machine.cycles * Drives::Model2031::CLOCK_HZ / machine.timing.clock_hz
123
+
124
+ # DRIVECPU0 for a drive CPU whose BL- fields `input` holds, with
125
+ # `ram` the drive's RAM as xpet lays it out: the drive's clock,
126
+ # the registers, the machine's clock, the drive's clock again, and
127
+ # the RAM.
128
+ def cpu_section(machine, input, ram)
129
+ clock = drive_clock(machine)
130
+ fields = FieldWriter.new.qword(clock)
131
+ %w[a x y sp].each { |key| fields.byte(input.int(key, 0)) }
132
+ fields.word(input.int("pc", 0)).byte(input.int("p", 0x20)).dword(0)
133
+ fields.qword(machine.cycles).zeros(16).qword(clock).zeros(1).qword(0).zeros(8).qword(0)
134
+ fields.zeros(16).bytes(ram).zeros(TAIL)
135
+ fields.section(CPU, CPU_MAJOR, CPU_MINOR)
136
+ end
137
+
138
+ # The 2031's keyed modules, named for device 8, from the
139
+ # snapshot's, with `disk` the drive's disk module badline keeps,
140
+ # or nil.
141
+ def import(container, disk)
142
+ via2 = container[VIA2]
143
+ sections = [Keyed.section("BL-D8") { |out| drive(container, out) },
144
+ Keyed.section("BL-D8-RAM") { |out| out.bytes("ram", ram(container[CPU])) },
145
+ Keyed.section("BL-D8-VIA1") { |out| VIA.import(container[VIA1], out) },
146
+ Keyed.section("BL-D8-VIA2") { |out| VIA.import(via2, out) },
147
+ Keyed.section("BL-D8-CPU") { |out| registers(container[CPU], out) },
148
+ Keyed.section("BL-D8-MECH") { |out| mechanism(container, via2, out) }]
149
+ disk ? sections + [disk] : sections
150
+ end
151
+
152
+ def drive(container, out)
153
+ out.text("model", "2031").int("phase", 0).int("cycles", FieldReader.new(container[CPU]).qword)
154
+ out.bool("so_pending", false)
155
+ end
156
+
157
+ def ram(section) = FieldReader.new(section).skip(RAM_AT).string(RAM_SIZE)
158
+
159
+ def registers(section, out)
160
+ fields = FieldReader.new(section)
161
+ clock = fields.qword
162
+ out.int("a", fields.byte).int("x", fields.byte).int("y", fields.byte).int("sp", fields.byte)
163
+ out.int("pc", fields.word).int("p", fields.byte).int("cycles", clock)
164
+ end
165
+
166
+ # The head where DRIVE8 has it, and the stepper's phase as VIA 2
167
+ # drives it, which the half track's low two bits stand for.
168
+ def mechanism(container, via2, out)
169
+ half_track = FieldReader.new(container[UNITS.first]).skip(HALF_TRACK).byte
170
+ bytes = FieldReader.new(via2).bytes(4)
171
+ port_b = bytes[2] | (~bytes[3] & 0xff)
172
+ out.int("half_track", half_track).int("slip", SLIP)
173
+ out.int("zone", (port_b >> 5) & 0x03)
174
+ out.bool("motor", port_b.anybits?(MOTOR)).bool("led", port_b.anybits?(LED))
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # A true 8050 or 8250 on device 8, as xpet 3.10, xcbm2 and xcbm5x0 save it with true
8
+ # drive emulation: DRIVE8 2.0 with both drives, DRIVE9 to DRIVE11
9
+ # with none, DRIVECPU0 1.3, the interface processor and its RAM,
10
+ # RIOT1D0 and RIOT2D0 0.0, its two 6532s, and FDC0 0.0. xpet runs
11
+ # the floppy controller at a high level, so its 6504, VIA and 6530
12
+ # aren't in the snapshot: FDC0 holds the controller's state, its
13
+ # last track in byte 6.
14
+ #
15
+ # DRIVE8 holds a block of 139 bytes for each drive, then 6 for the
16
+ # unit: each block has its head's half track, twice the track, at
17
+ # $10 and the drive's type as a word at $28 (8050 or 8250).
18
+ # DRIVECPU0 is laid out as the 2031's (Drive2031), its RAM the IP's
19
+ # page 0, the 6532s' 128 bytes each, then the 4K of common RAM.
20
+ # A RIOT module holds port A's output and direction registers, port
21
+ # B's, the edge control (bit 0 the PA7 interrupt, bit 1 a rising
22
+ # edge), the flags (bit 7 the timer's interrupt, which xpet raises
23
+ # IRQ for, bit 6 PA7's), the timer's
24
+ # counter, its divider as a word, two bytes xpet leaves 0, and
25
+ # whether the timer interrupts.
26
+ #
27
+ # Written, once the controller is in its idle loop between
28
+ # instructions (PET#settle_for_vice!): the IP's registers and RAM,
29
+ # the 6532s, the heads' tracks, and FDC0 as xpet leaves an idle
30
+ # controller with the track badline's controller last stepped to.
31
+ # A unit whose controller is still busy is left out of xpet's
32
+ # modules. Read: the IP's registers and RAM and the 6532s, with the
33
+ # controller as a unit switched on has it in its idle loop, its
34
+ # heads on track 1, from where its next job steps them. A job under
35
+ # way in xpet's controller starts again from there. The disks in
36
+ # the drives stay.
37
+ module Drive8050
38
+ UNIT = "DRIVE8"
39
+ CPU = Drive2031::CPU
40
+ RIOTS = %w[RIOT1D0 RIOT2D0].freeze
41
+ FDC = "FDC0"
42
+ NAMES = [UNIT, CPU, *RIOTS, FDC].freeze
43
+ TYPES = { 0x1f72 => "8050", 0x203a => "8250" }.freeze
44
+ # Where each drive's block starts in DRIVE8, and its fields.
45
+ DRIVES = [0x00, 0x8b].freeze
46
+ HALF_TRACK = 0x10
47
+ TYPE = 0x28
48
+ SIZE = 284
49
+ # DRIVE8 as xpet saves an idle 8050, by the bytes that aren't 0,
50
+ # but for the type.
51
+ IDLE = { 0x00 => 0x01, 0x01 => 0x02, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x4c, 0x1a => 0x01,
52
+ 0x20 => 0x55, 0x30 => 0x06, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12, 0x99 => 0x01,
53
+ 0x9a => 0x01, 0x9b => 0x24, 0xa5 => 0x01, 0xab => 0x55, 0x11a => 0x01 }.freeze
54
+ ZERO_PAGE = 0x80
55
+ COMMON = 0x1000
56
+ # FDC0 as xpet leaves an idle controller, and where the track goes.
57
+ FDC_IDLE = [0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00].freeze
58
+ FDC_TRACK = 6
59
+ # Where badline's controller keeps drive 0's track, in the 6530's
60
+ # RAM.
61
+ TRACK_AT = 0x21
62
+ DIVIDERS = [1, 8, 64, 1024].freeze
63
+ IRQ_EDGE = 0x01
64
+ RISING = 0x02
65
+ TIMER_FLAG = 0x80
66
+ PA7_FLAG = 0x40
67
+
68
+ module_function
69
+
70
+ # Whether the snapshot has a true 8050 or 8250.
71
+ def present?(container) = !container[FDC].nil? && !container[CPU].nil?
72
+
73
+ def reads?(section)
74
+ case section.name
75
+ when CPU then Drive2031.reads?(section)
76
+ when UNIT then section.major == Drive2031::DRIVE_MAJOR && section.minor == Drive2031::DRIVE_MINOR
77
+ else section.major.zero? && section.minor.zero?
78
+ end
79
+ end
80
+
81
+ # The model DRIVE8 names, or nil.
82
+ def model(container)
83
+ unit = container[UNIT]
84
+ return nil if unit.nil? || unit.data.bytesize < SIZE
85
+
86
+ TYPES[FieldReader.new(unit).skip(TYPE).word]
87
+ end
88
+
89
+ # xpet's modules for the 8050 or 8250 whose BL-D8 modules `readers`
90
+ # holds, and none without one, or with its controller away from
91
+ # its idle loop.
92
+ def export(machine, readers)
93
+ drive = readers["BL-D8"]
94
+ model = drive&.text("model", "")
95
+ return [] unless TYPES.value?(model) && idle?(readers)
96
+
97
+ [unit(model, readers.fetch("BL-D8-MECH")), *Drive2031::UNITS.drop(1).map { |name| none(name) },
98
+ Drive2031.cpu_section(machine, readers.fetch("BL-D8-IPCPU"), ram(readers)),
99
+ riot(RIOTS[0], readers.fetch("BL-D8-RIOT1")), riot(RIOTS[1], readers.fetch("BL-D8-RIOT2")),
100
+ fdc(readers.fetch("BL-D8-RRIOT"))]
101
+ end
102
+
103
+ # Whether both processors are between instructions, the
104
+ # controller at the start of its idle loop.
105
+ def idle?(readers)
106
+ fdc = readers.fetch("BL-D8-FDCCPU")
107
+ !position?(fdc) && !position?(readers.fetch("BL-D8-IPCPU")) &&
108
+ fdc.int("pc", 0) == Drives::Model8050::Idle::FDC_LOOP
109
+ end
110
+
111
+ def position?(cpu) = !cpu.text("pos.seq", "").empty?
112
+
113
+ def unit(model, mechanism)
114
+ bytes = Array.new(SIZE) { |offset| IDLE.fetch(offset, 0) }
115
+ cylinders = mechanism.ints("cylinders", [0, 0])
116
+ DRIVES.each_with_index do |at, drive|
117
+ bytes[at + HALF_TRACK] = ((cylinders[drive] || 0) + 1) * 2
118
+ bytes[at + TYPE, 2] = [TYPES.key(model)].pack("v").bytes
119
+ end
120
+ FieldWriter.new.bytes(bytes).section(UNIT, Drive2031::DRIVE_MAJOR, Drive2031::DRIVE_MINOR)
121
+ end
122
+
123
+ def none(name) = Drive2031.unit(Drive2031::NONE, name)
124
+
125
+ # The IP's RAM as xpet lays it out: page 0 from both 6532s, then
126
+ # the common RAM.
127
+ def ram(readers)
128
+ [readers.fetch("BL-D8-RIOT1"), readers.fetch("BL-D8-RIOT2")].flat_map do |riot|
129
+ filled(riot.bytes("ram", "".b).bytes, ZERO_PAGE)
130
+ end + filled(readers.fetch("BL-D8-COMMON").bytes("ram", "".b).bytes, COMMON)
131
+ end
132
+
133
+ def filled(bytes, length) = Array.new(length) { |index| bytes[index] || 0 }
134
+
135
+ def riot(name, input)
136
+ edge = input.bool("edge_irq_enabled", false) ? IRQ_EDGE : 0
137
+ edge |= RISING if input.bool("rising_edge", false)
138
+ timer = input.bool("timer.flag", false) && input.bool("timer.irq_enabled", false)
139
+ flags = (timer ? TIMER_FLAG : 0) | (input.bool("pa7_flag", false) ? PA7_FLAG : 0)
140
+ divider = DIVIDERS.fetch(input.int("timer.interval", 0) & 0x03)
141
+ fields = FieldWriter.new.byte(input.int("ora", 0)).byte(input.int("ddra", 0)).byte(input.int("orb", 0))
142
+ fields.byte(input.int("ddrb", 0)).byte(edge).byte(flags).byte(input.int("timer.counter", 0)).word(divider)
143
+ fields.zeros(2).flag(input.bool("timer.irq_enabled", false)).section(name, 0, 0)
144
+ end
145
+
146
+ def fdc(rriot)
147
+ bytes = FDC_IDLE.dup
148
+ bytes[FDC_TRACK] = rriot.bytes("ram", "".b).getbyte(TRACK_AT).to_i
149
+ FieldWriter.new.bytes(bytes).section(FDC, 0, 0)
150
+ end
151
+
152
+ # The unit's keyed modules, named for device 8, from the
153
+ # snapshot's: the IP's side from xpet's, the controller's side
154
+ # from `reference`, a unit's in its idle loop, and `disks`, the
155
+ # drives' disk modules badline keeps.
156
+ def import(container, reference, disks)
157
+ cpu = container[CPU]
158
+ page = FieldReader.new(cpu).skip(Drive2031::RAM_AT).string(ZERO_PAGE * 2)
159
+ common = FieldReader.new(cpu).skip(Drive2031::RAM_AT + (ZERO_PAGE * 2)).string(COMMON)
160
+ sections = [Keyed.section("BL-D8") { |out| unit_fields(container, reference, out) },
161
+ Keyed.section("BL-D8-COMMON") { |out| out.bytes("ram", common) },
162
+ riot_section("BL-D8-RIOT1", container[RIOTS[0]], page.byteslice(0, ZERO_PAGE)),
163
+ riot_section("BL-D8-RIOT2", container[RIOTS[1]], page.byteslice(ZERO_PAGE, ZERO_PAGE)),
164
+ Keyed.section("BL-D8-IPCPU") { |out| Drive2031.registers(cpu, out) }]
165
+ sections + reference.select { |section| CONTROLLER.include?(section.name) } + disks
166
+ end
167
+
168
+ # The controller's side, which comes from a unit in its idle loop.
169
+ CONTROLLER = %w[BL-D8-FDCCPU BL-D8-VIA BL-D8-RRIOT BL-D8-MECH].freeze
170
+
171
+ def unit_fields(container, reference, out)
172
+ own = Keyed::Reader.parse(reference.find { |section| section.name == "BL-D8" })
173
+ out.text("model", own.text("model", "8050")).int("phase", 0)
174
+ out.int("cycles", FieldReader.new(container[CPU]).qword).int("fdc_data", own.int("fdc_data", 0))
175
+ end
176
+
177
+ def riot_section(name, section, ram)
178
+ fields = FieldReader.new(section)
179
+ ora, ddra, orb, ddrb, edge, flags, counter = fields.bytes(7)
180
+ divider = fields.word
181
+ fields.skip(2)
182
+ irq = fields.flag?
183
+ Keyed.section(name) do |out|
184
+ out.bytes("ram", ram).int("ora", ora).int("ddra", ddra).int("orb", orb).int("ddrb", ddrb)
185
+ out.bool("edge_irq_enabled", edge.anybits?(IRQ_EDGE)).bool("rising_edge", edge.anybits?(RISING))
186
+ out.bool("pa7_flag", flags.anybits?(PA7_FLAG)).int("timer.counter", counter)
187
+ out.int("timer.interval", DIVIDERS.index(divider) || 0).int("timer.prescale", [divider, 1].max)
188
+ out.bool("timer.flag", flags.anybits?(TIMER_FLAG)).bool("timer.expired", flags.anybits?(TIMER_FLAG))
189
+ out.bool("timer.irq_enabled", irq)
190
+ end
191
+ end
192
+
193
+ # A unit of `model` switched on and run into its controller's idle
194
+ # loop, both processors between instructions, as keyed modules
195
+ # named for device 8, run once a model.
196
+ def reference(model)
197
+ @references ||= {}
198
+ @references[model] ||= booted(model)
199
+ end
200
+
201
+ def booted(model)
202
+ unit = model == "8250" ? Drives::Model8250.new : Drives::Model8050.new
203
+ Drives::Model8050::BOOT_CYCLES.times { unit.host_cycle! }
204
+ unit.cycle! until idle_loop?(unit)
205
+ unit.save_modules([], "BL-D8")
206
+ end
207
+
208
+ def idle_loop?(unit)
209
+ cpu = unit.fdc_cpu
210
+ unit.ip_cpu.boundary? && cpu.boundary? && cpu.program_counter == Drives::Model8050::Idle::FDC_LOOP
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # PETMEM 1.3: the board, the RAM below $8000 in K and the 8096's
8
+ # control register at $FFF0, then the RAM, the screen RAM, 1K or 2K
9
+ # as the board has, or on the 8296 its 32K from $8000, an 8096's or
10
+ # 8296's 64K board, and 24 bytes of xpet's own settings, which name
11
+ # the 2001.
12
+ #
13
+ # The board is 0 for the discrete video, 1 for a 40-column CRTC, 2
14
+ # for an 80-column one, 3 for the SuperPET, 4 for the 8096 and 5 for
15
+ # the 8296. xpet's ROMs aren't in the snapshot.
16
+ module PETMem
17
+ NAME = "PETMEM"
18
+ MAJOR = 1
19
+ MINOR = 3
20
+ DISCRETE = 0
21
+ CRTC40 = 1
22
+ CRTC80 = 2
23
+ SUPERPET = 3
24
+ CBM8096 = 4
25
+ CBM8296 = 5
26
+ # The byte after the header's control register, as xpet writes it.
27
+ HEADER_END = 0x05
28
+ # xpet's settings at the end, but for the second byte, which is
29
+ # SETTINGS_2001 on a 2001.
30
+ SETTINGS = [0x00, 0x00, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00,
31
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00].freeze
32
+ SETTINGS_2001 = 0x03
33
+ EXPANSION = 0x10000
34
+
35
+ module_function
36
+
37
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
38
+
39
+ def board(model)
40
+ return CBM8296 if model.cbm8296?
41
+ return CBM8096 if model.expansion
42
+
43
+ case model.video
44
+ when :crtc40 then CRTC40
45
+ when :crtc80 then CRTC80
46
+ else DISCRETE
47
+ end
48
+ end
49
+
50
+ def export(machine)
51
+ bus = machine.bus
52
+ expansion = bus.expansion
53
+ fields = FieldWriter.new.byte(board(machine.model)).byte(0).byte(machine.ram_kb)
54
+ fields.byte(expansion ? expansion.control : 0).byte(HEADER_END)
55
+ fields.bytes(machine.ram.snapshot).bytes(bus.screen.cells)
56
+ fields.bytes(expansion.cells) if expansion
57
+ settings = SETTINGS.dup
58
+ settings[1] = SETTINGS_2001 if machine.model.basic1?
59
+ fields.bytes(settings).section(NAME, MAJOR, MINOR)
60
+ end
61
+
62
+ # The board's number in the header.
63
+ def board_of(section) = FieldReader.new(section).byte
64
+
65
+ def ram_kb(section) = FieldReader.new(section).skip(2).byte
66
+
67
+ # Whether xpet's settings at the end name a 2001.
68
+ def pet2001?(section)
69
+ data = section.data
70
+ data.bytesize >= SETTINGS.length && data.getbyte(data.bytesize - SETTINGS.length + 1).to_i.anybits?(0x01)
71
+ end
72
+
73
+ # The RAM, the screen RAM and an 8096's or 8296's board and control
74
+ # register
75
+ # by their BL-RAM keys, in a keyed module's writer, for a machine
76
+ # with `screen_bytes` of screen RAM.
77
+ def import(section, out, screen_bytes)
78
+ fields = FieldReader.new(section)
79
+ board = fields.byte
80
+ fields.skip(1)
81
+ ram_kb = fields.byte
82
+ control = fields.byte
83
+ fields.skip(1)
84
+ out.bytes("ram", fields.string(ram_kb * 1024)).bytes("screen", fields.string(screen_bytes))
85
+ return unless [CBM8096, CBM8296].include?(board)
86
+
87
+ out.int("expansion.control", control).bytes("expansion.ram", fields.string(EXPANSION))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # PIA1 and PIA2 1.0: port A's output and direction registers and
8
+ # its control register as read, with the CA1 and CA2 flags in bits 7
9
+ # and 6, the same for port B, and the levels on CA2 and CB2 in bits
10
+ # 7 and 6 of the last byte, high while a line is an input.
11
+ #
12
+ # Read: each side's flags come from its control register, and a
13
+ # handshake under way from the level its C2 output is at. The levels
14
+ # outside on CA1 and CB1 aren't saved: they stay as the machine has
15
+ # them, with PIA 1's CB1 on the vertical drive line.
16
+ module PIAs
17
+ NAMES = %w[PIA1 PIA2].freeze
18
+ MAJOR = 1
19
+ MINOR = 0
20
+ CA2_HIGH = 0x80
21
+ CB2_HIGH = 0x40
22
+ HANDSHAKE = 0x20
23
+
24
+ module_function
25
+
26
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
27
+
28
+ # The PIA's module, from its BL-PIA1 or BL-PIA2 fields in `input`.
29
+ def export(name, pia, input)
30
+ fields = FieldWriter.new.byte(input.int("ora", 0)).byte(input.int("ddra", 0)).byte(pia.cra)
31
+ fields.byte(input.int("orb", 0)).byte(input.int("ddrb", 0)).byte(pia.crb)
32
+ fields.byte((pia.ca2_output ? CA2_HIGH : 0) | (pia.cb2_output ? CB2_HIGH : 0))
33
+ fields.section(name, MAJOR, MINOR)
34
+ end
35
+
36
+ # The registers and the control lines by their BL-PIA keys, in a
37
+ # keyed module's writer.
38
+ def import(section, out)
39
+ ora, ddra, cra, orb, ddrb, crb, lines = FieldReader.new(section).bytes(7)
40
+ out.int("ora", ora).int("ddra", ddra).int("orb", orb).int("ddrb", ddrb)
41
+ control_lines(out, "ca.", cra, lines.anybits?(CA2_HIGH))
42
+ control_lines(out, "cb.", crb, lines.anybits?(CB2_HIGH))
43
+ end
44
+
45
+ def control_lines(out, prefix, register, c2_high)
46
+ control = register & 0x3f
47
+ out.int("#{prefix}control", control).bool("#{prefix}c1_flag", register.anybits?(0x80))
48
+ out.bool("#{prefix}c2_flag", register.anybits?(0x40)).int("#{prefix}pulse", 0)
49
+ out.bool("#{prefix}handshake_low", control & 0x38 == HANDSHAKE && !c2_high)
50
+ out.bool("#{prefix}c2_high", c2_high) if control.nobits?(0x20)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end