badline 0.17.0 → 0.19.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/snapshot/vice/cbm2_acia"
3
4
  require "badline/snapshot/vice/pet_cpu"
5
+ require "badline/snapshot/vice/pet_dongle"
4
6
  require "badline/snapshot/vice/pet_mem"
5
7
  require "badline/snapshot/vice/pet_pias"
6
8
  require "badline/snapshot/vice/pet_via"
@@ -22,8 +24,8 @@ module Badline
22
24
  # The models badline builds by the board PETMEM names (PETMem): the
23
25
  # discrete board is a 2001's where xpet's settings say so, and a
24
26
  # 3032 otherwise.
25
- MODELS = { PETMem::CRTC40 => "4032", PETMem::CRTC80 => "8032", PETMem::CBM8096 => "8096",
26
- PETMem::CBM8296 => "8296" }.freeze
27
+ MODELS = { PETMem::CRTC40 => "4032", PETMem::CRTC80 => "8032", PETMem::SUPERPET => "superpet",
28
+ PETMem::CBM8096 => "8096", PETMem::CBM8296 => "8296" }.freeze
27
29
  # The modules xpet resets the machine without, for the parts badline
28
30
  # doesn't model in VICE's terms, each as xpet leaves them with
29
31
  # nothing attached: no file system drive open, both cassette ports
@@ -50,7 +52,8 @@ module Badline
50
52
  PIAs.export("PIA1", machine.pia1, reader(readers, "BL-PIA1")),
51
53
  PIAs.export("PIA2", machine.pia2, reader(readers, "BL-PIA2")), VIA.export(reader(readers, "BL-VIA")),
52
54
  *Drive2031.export(machine, readers), *Drive8050.export(machine, readers)] +
53
- PERIPHERALS.map { |name, major, minor, bytes| FieldWriter.new.bytes(bytes).section(name, major, minor) }
55
+ PERIPHERALS.map { |name, major, minor, bytes| FieldWriter.new.bytes(bytes).section(name, major, minor) } +
56
+ (machine.superpet? ? [CBM2::ACIA.export(reader(readers, "BL-ACIA"), machine.cycles)] : [])
54
57
  end
55
58
 
56
59
  def reader(readers, name) = readers[name] || Keyed::Reader.missing(name)
@@ -61,7 +64,9 @@ module Badline
61
64
  # xpet doesn't save the keyboard, the ROMs or an 8296's jumpers, so
62
65
  # a discrete board is a 3032 with the graphics keyboard unless it is
63
66
  # a 2001, each model has its own ROMs, and an 8296 none of its
64
- # jumpers, as xpet's own has.
67
+ # jumpers, as xpet's own has. Nor does it save the SuperPET's
68
+ # switches: its CPU switch is at the CPU that has the bus, and its
69
+ # RAM switch at R/W, which is the only position xpet has.
65
70
  def setup(container)
66
71
  memory = memory(container)
67
72
  ram_kb = PETMem.ram_kb(memory)
@@ -75,8 +80,11 @@ module Badline
75
80
  crtc = nil unless crtc.nil? || CRTC.reads?(crtc)
76
81
  refresh_hz = refresh_hz(crtc, profile)
77
82
  profile = Badline::PET::Model.upgraded(profile, nil, editor(crtc, profile, refresh_hz))
83
+ superpet = profile.superpet?
84
+ cpu_switch = PETMem.m6809?(memory) ? "6809" : "6502"
78
85
  PETSetup.new(model: name, ram_kb:, refresh_hz:, basic: profile.basic, editor: profile.editor(refresh_hz),
79
- kernal: profile.kernal, characters: profile.characters, jumpers: "")
86
+ kernal: profile.kernal, characters: profile.characters, jumpers: "",
87
+ cpu_switch: superpet ? cpu_switch : "", ram_switch: superpet ? "rw" : "")
80
88
  end
81
89
 
82
90
  def memory(container)
@@ -94,8 +102,7 @@ module Badline
94
102
  name = MODELS[board]
95
103
  return name unless name.nil?
96
104
 
97
- what = board == PETMem::SUPERPET ? "a SuperPET" : "board #{board}"
98
- raise FormatError, "#{memory}: #{what}, which badline doesn't build"
105
+ raise FormatError, "#{memory}: board #{board}, which badline doesn't build"
99
106
  end
100
107
 
101
108
  # The editor's frame rate, from the cycles the CRTC's registers set
@@ -25,7 +25,11 @@ module Badline
25
25
 
26
26
  def reads?(section) = section.major == MAJOR && section.minor == MINOR
27
27
 
28
- def export(machine) = write(machine, [machine.pia1.irq?, machine.pia2.irq?, machine.via.irq?].count(true))
28
+ def export(machine)
29
+ sources = [machine.pia1.irq?, machine.pia2.irq?, machine.via.irq?]
30
+ sources << machine.acia_irq? if machine.superpet?
31
+ write(machine, sources.count(true))
32
+ end
29
33
 
30
34
  # The module for `machine`, whose CPU's IRQ line `irqs` sources
31
35
  # pull.
@@ -52,16 +56,46 @@ module Badline
52
56
  end
53
57
 
54
58
  # The SuperPET's 6809, CPU6809 1.1, which xpet saves on every model
55
- # and won't load a snapshot without: the clock and the registers as
56
- # an xpet with no SuperPET board leaves them. badline doesn't read
57
- # it.
59
+ # and won't load a snapshot without: the clock, X, Y, U, S and PC,
60
+ # DP, CC, A and B, then five bytes. xpet 3.10's layout was inferred
61
+ # from its snapshots of a 6809 running a loop that loads each
62
+ # register with a value of its own; the five bytes are 0 in every
63
+ # snapshot xpet wrote.
64
+ #
65
+ # Written: the clock and the registers, the SuperPET's 6809's
66
+ # between instructions while it has the bus, and otherwise the
67
+ # registers of a 6809 that hasn't run, as xpet leaves them. Read:
68
+ # the registers, with the 6809 on an instruction boundary, as
69
+ # xpet's snapshot is taken on one.
58
70
  module CPU6809
59
71
  NAME = "CPU6809"
72
+ MAJOR = 1
73
+ MINOR = 1
74
+ # The CC an xpet without a SuperPET board saves.
75
+ IDLE_CC = 0x04
60
76
 
61
77
  module_function
62
78
 
79
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
80
+
63
81
  def export(machine)
64
- FieldWriter.new.qword(machine.cycles).zeros(11).byte(0x04).zeros(7).section(NAME, 1, 1)
82
+ fields = FieldWriter.new.qword(machine.cycles)
83
+ unless machine.superpet? && machine.m6809_running?
84
+ return fields.zeros(11).byte(IDLE_CC).zeros(7).section(NAME, MAJOR, MINOR)
85
+ end
86
+
87
+ cpu = machine.m6809
88
+ [cpu.x, cpu.y, cpu.u, cpu.s, cpu.pc].each { |register| fields.word(register) }
89
+ fields.byte(cpu.dp).byte(cpu.cc).byte(cpu.a).byte(cpu.b).zeros(5).section(NAME, MAJOR, MINOR)
90
+ end
91
+
92
+ # The registers by their BL-M6809 keys, in a keyed module's writer,
93
+ # with the 6809's cycle count the machine's `cycles`.
94
+ def import(section, out, cycles)
95
+ fields = FieldReader.new(section).skip(8)
96
+ %w[x y u s pc].each { |name| out.int(name, fields.word) }
97
+ %w[dp cc a b].each { |name| out.int(name, fields.byte) }
98
+ out.int("cycles", cycles).bool("nmi.armed", true)
65
99
  end
66
100
  end
67
101
  end
@@ -6,7 +6,7 @@ module Badline
6
6
  module PET
7
7
  # CRTC 1.2, which xpet saves on every model, the discrete boards'
8
8
  # video too, as a 6545 with the registers fixed: the board's screen
9
- # settings, the registers, the one selected, the beam's character,
9
+ # settings, the character set VIA CA2 picks, the registers, the one selected, the beam's character,
10
10
  # row and line in the row, whether the frame is in its vertical
11
11
  # adjust, the row's start in screen RAM, the lines of vertical sync
12
12
  # left, whether the rows show, the picture's size, the lines in the
@@ -36,6 +36,13 @@ module Badline
36
36
  SETTINGS = [0xff, 0x03, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0xff, 0x1f].freeze
37
37
  # The 8296's, whose screen RAM spans 4K.
38
38
  SETTINGS_8296 = [0xff, 0x0f, 0x00, 0x20, 0x00, 0x20, 0xff, 0x0f, 0xff, 0x1f].freeze
39
+ # The SuperPET's, whose character ROM spans 4K, inferred from
40
+ # xpet 3.10's SuperPET snapshots.
41
+ SETTINGS_SUPERPET = [0xff, 0x03, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0xff, 0x3f].freeze
42
+ # The word after the settings while VIA CA2 is high, picking the
43
+ # character ROM's second set, as xpet 3.10 writes it and paints
44
+ # the picture from on every PET board.
45
+ SECOND_SET = 0x1000
39
46
  # The 2001's and 3000 series' video as xpet's CRTC runs it: 64
40
47
  # cycles a line, 32 rows of 8 lines and 4 more, the first 25 rows
41
48
  # shown.
@@ -85,7 +92,7 @@ module Badline
85
92
  crtc_beam(run_on(fresh_crtc(machine), crtc_input, -CRTC_LEAD % frame_cycles(registers)),
86
93
  registers)
87
94
  end
88
- fields = FieldWriter.new.bytes(model.cbm8296? ? SETTINGS_8296 : SETTINGS).word(attributes(model))
95
+ fields = FieldWriter.new.bytes(settings(model)).word(machine.via.ca2_output ? SECOND_SET : 0)
89
96
  fields.byte(0).byte(columns(model))
90
97
  fields.byte(model.basic1? ? 1 : 0).bytes(registers).byte(discrete ? 0 : crtc_input.int("selected", 0))
91
98
  beam_fields(fields, beam, registers, model)
@@ -93,6 +100,12 @@ module Badline
93
100
  fields.section(NAME, MAJOR, MINOR)
94
101
  end
95
102
 
103
+ def settings(model)
104
+ return SETTINGS_8296 if model.cbm8296?
105
+
106
+ model.superpet? ? SETTINGS_SUPERPET : SETTINGS
107
+ end
108
+
96
109
  def crtc_registers(input)
97
110
  registers = input.bytes("registers", "".b).bytes
98
111
  Array.new(REGISTERS) { |index| registers[index] || 0 }
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # The SuperPET's 6702 in PETMEM's settings at the end, from their
8
+ # fifth byte: the byte the 6702 reads, the last write that shifted
9
+ # it, 1 while the last write left D0 clear, then a little-endian word
10
+ # for each data line's shift register. Bit k of a word, below the
11
+ # register's last stage, is stage k XORed with stage k + 1, and its
12
+ # last stage's bit is that stage XORed with the stage the next shift
13
+ # brings in if the latch then holds the last write that shifted.
14
+ # Every PET's settings hold a 6702 as the RES line leaves it.
15
+ #
16
+ # Mapped from xpet 3.10's snapshots after each of 56 writes to $EFE0
17
+ # from its monitor, against PET::SuperPET::Dongle after the same
18
+ # writes.
19
+ #
20
+ # Written and read: the registers, and whether D0 was clear in the
21
+ # last write. A write the 6702 hasn't shifted on yet is written as
22
+ # shifted. Read, the latch is $00 or $FF by D0, and no shift waits.
23
+ module Dongle
24
+ # Offsets in PETMEM's settings.
25
+ AT = 4
26
+ LENGTH = 19
27
+ OUTPUT = 0
28
+ ARMED = 2
29
+ WORDS = 3
30
+ # The last write that shifted, as xpet has it after RES.
31
+ RESET_LAST = 0x01
32
+
33
+ module_function
34
+
35
+ # The settings' bytes for a 6702 with `registers`, after a write
36
+ # of `latch`.
37
+ def export(registers, latch)
38
+ incoming = RESET_LAST ^ Badline::PET::SuperPET::Dongle::FEEDBACK ^ 0xff
39
+ output = 0
40
+ words = []
41
+ 8.times do |bit|
42
+ register = registers[bit]
43
+ output |= (register & 1) << bit
44
+ top = Badline::PET::SuperPET::Dongle::LENGTHS[bit] - 1
45
+ last = ((register >> top) ^ register ^ (incoming >> bit)) & 1
46
+ word = ((register ^ (register >> 1)) & ((1 << top) - 1)) | (last << top)
47
+ words << (word & 0xff) << (word >> 8)
48
+ end
49
+ [output ^ Badline::PET::SuperPET::Dongle::INVERTED, RESET_LAST, latch.nobits?(0x01) ? 1 : 0, *words]
50
+ end
51
+
52
+ # The 6702's BL-SUPERPET keys from the settings' `bytes`, in a keyed
53
+ # module's writer.
54
+ def import(bytes, out)
55
+ stages = bytes[OUTPUT] ^ Badline::PET::SuperPET::Dongle::INVERTED
56
+ registers = Array.new(8) do |bit|
57
+ word = bytes[WORDS + (bit * 2)] | (bytes[WORDS + (bit * 2) + 1] << 8)
58
+ stage = (stages >> bit) & 1
59
+ register = stage
60
+ (Badline::PET::SuperPET::Dongle::LENGTHS[bit] - 1).times do |index|
61
+ stage ^= (word >> index) & 1
62
+ register |= stage << (index + 1)
63
+ end
64
+ register
65
+ end
66
+ out.ints("dongle.registers", registers).int("dongle.latch", bytes[ARMED].zero? ? 0xff : 0x00)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -4,70 +4,96 @@ module Badline
4
4
  module Snapshot
5
5
  module Vice
6
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.
7
+ # A true unit with an interface processor on device 8, as xpet 3.10,
8
+ # xcbm2 and xcbm5x0 save it with true drive emulation: an 8050, an
9
+ # 8250, an SFD-1001, a 4040, a 3040, a 2040, or a D9060 or D9090,
10
+ # which xpet's type 9000 stands for. Each is DRIVE8 2.0 with the
11
+ # unit's drives, DRIVE9 to DRIVE11 with none, DRIVECPU0 1.3, the
12
+ # interface processor and its RAM, RIOT1D0 and RIOT2D0 0.0, its two
13
+ # 6532s, and FDC0 0.0. xpet runs the floppy controller and the hard
14
+ # disk's SASI host at a high level, so their processors, VIAs and
15
+ # 6530s aren't in the snapshot: FDC0 holds the controller's state,
16
+ # its last track in byte 6.
14
17
  #
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
18
+ # DRIVE8 holds a block of 139 bytes for each of the unit's drives,
19
+ # then 6 for the unit: each block has its head's half track, twice
20
+ # the track, at $10 and the unit's type as a word at $28 (TYPES).
21
+ # The 1001 and the hard disks have one block, and a hard disk's head
22
+ # rests at HARD_DISK_TRACK. DRIVECPU0 is laid out as the 2031's
23
+ # (Drive2031), its RAM the IP's page 0, the 6532s' 128 bytes each,
24
+ # then the 4K of common RAM. A RIOT module holds port A's output and
25
+ # direction registers, port B's, the edge control (bit 0 the PA7
26
+ # interrupt, bit 1 a rising edge), the flags (bit 7 the timer's
27
+ # interrupt, which xpet raises IRQ for, bit 6 PA7's), the timer's
24
28
  # counter, its divider as a word, two bytes xpet leaves 0, and
25
29
  # whether the timer interrupts.
26
30
  #
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.
31
+ # Written, once the controller, or the SASI host, is in its idle
32
+ # loop between instructions (PET#settle_for_vice!): the IP's
33
+ # registers and RAM, the 6532s, the heads' tracks, and FDC0 as xpet
34
+ # leaves an idle controller with the track badline's controller last
35
+ # stepped to. A unit whose controller is still busy is left out of
36
+ # xpet's modules. Read: the IP's registers and RAM and the 6532s,
37
+ # with the controller as a unit switched on has it in its idle loop,
38
+ # its heads on track 1, from where its next job steps them. A job
39
+ # under way in xpet's controller starts again from there. The disks
40
+ # in the drives stay, and a hard disk's image with them, which also
41
+ # says whether the unit is a D9060 or a D9090.
37
42
  module Drive8050
38
43
  UNIT = "DRIVE8"
39
44
  CPU = Drive2031::CPU
40
45
  RIOTS = %w[RIOT1D0 RIOT2D0].freeze
41
46
  FDC = "FDC0"
42
47
  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
48
+ # xpet's drive types by DRIVE8's type word, for the units written
49
+ # and read here.
50
+ TYPES = { 0x1f72 => "8050", 0x203a => "8250", 0x03e9 => "1001", 0x0fc8 => "4040", 0x0be0 => "3040",
51
+ 0x07f8 => "2040", 0x2328 => "9060" }.freeze
52
+ # The type word each model is written with.
53
+ WORDS = TYPES.invert.merge("9090" => 0x2328).freeze
54
+ # The units that are hard disks, and the DOS 2.1 and DOS 1 units
55
+ # on the 4040's board.
56
+ HARD_DISKS = %w[9060 9090].freeze
57
+ DOS2_BOARD = %w[4040 3040 2040].freeze
58
+ # The drives each unit has, a block of DRIVE8 each.
59
+ DRIVE_COUNTS = { "8050" => 2, "8250" => 2, "1001" => 1, "4040" => 2, "3040" => 2, "2040" => 2, "9060" => 1,
60
+ "9090" => 1 }.freeze
61
+ # A drive's block in DRIVE8, and its fields.
62
+ BLOCK = 0x8b
46
63
  HALF_TRACK = 0x10
47
64
  TYPE = 0x28
48
- SIZE = 284
65
+ # The half track xpet keeps a hard disk's head on.
66
+ HARD_DISK_TRACK = 0x98
67
+ # The unit's bytes after the drives' blocks.
68
+ TAIL = 6
49
69
  # 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
70
+ # but for the type and the drive count at COUNT: the first drive's
71
+ # block, and from BLOCK on the second's with a byte past it.
72
+ IDLE = { 0x00 => 0x01, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x4c, 0x1a => 0x01, 0x20 => 0x55,
73
+ 0x30 => 0x06, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12 }.freeze
74
+ IDLE_SECOND = { 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x24, 0x1a => 0x01, 0x20 => 0x55, 0x8f => 0x01 }.freeze
75
+ COUNT = 0x01
54
76
  ZERO_PAGE = 0x80
55
77
  COMMON = 0x1000
56
78
  # FDC0 as xpet leaves an idle controller, and where the track goes.
57
79
  FDC_IDLE = [0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00].freeze
58
80
  FDC_TRACK = 6
59
81
  # Where badline's controller keeps drive 0's track, in the 6530's
60
- # RAM.
82
+ # RAM: the 8050's, and the 4040's in the low six bits.
61
83
  TRACK_AT = 0x21
84
+ DOS2_TRACK_AT = 0x03
85
+ DOS2_TRACK = 0x3f
62
86
  DIVIDERS = [1, 8, 64, 1024].freeze
63
87
  IRQ_EDGE = 0x01
64
88
  RISING = 0x02
65
89
  TIMER_FLAG = 0x80
66
90
  PA7_FLAG = 0x40
91
+ # The hard disk's controller, which holds the image's path.
92
+ SASI = "BL-D8-SASI"
67
93
 
68
94
  module_function
69
95
 
70
- # Whether the snapshot has a true 8050 or 8250.
96
+ # Whether the snapshot has a true unit of these.
71
97
  def present?(container) = !container[FDC].nil? && !container[CPU].nil?
72
98
 
73
99
  def reads?(section)
@@ -78,48 +104,93 @@ module Badline
78
104
  end
79
105
  end
80
106
 
81
- # The model DRIVE8 names, or nil.
107
+ # The model DRIVE8 names, or nil. Type 9000 reads as the D9060.
82
108
  def model(container)
83
109
  unit = container[UNIT]
84
- return nil if unit.nil? || unit.data.bytesize < SIZE
110
+ return nil if unit.nil? || unit.data.bytesize < BLOCK
85
111
 
86
- TYPES[FieldReader.new(unit).skip(TYPE).word]
112
+ model = TYPES[FieldReader.new(unit).skip(TYPE).word]
113
+ model if model && unit.data.bytesize >= size(model)
87
114
  end
88
115
 
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.
116
+ # Whether xpet saves the two models as one type.
117
+ def same_type?(model, other) = WORDS.key?(model) && WORDS[model] == WORDS[other]
118
+
119
+ def hard_disk?(model) = HARD_DISKS.include?(model)
120
+
121
+ # The hard disk whose image the kept modules `disks` hold: a
122
+ # D9090 for one with six heads, and otherwise a D9060.
123
+ def hard_disk_model(disks)
124
+ own = disks.find { |section| section.name == SASI }
125
+ own && Keyed::Reader.parse(own).int("disk_heads", 4) == 6 ? "9090" : "9060"
126
+ end
127
+
128
+ # DRIVE8's size for the model.
129
+ def size(model) = (BLOCK * DRIVE_COUNTS.fetch(model)) + TAIL
130
+
131
+ # xpet's modules for the unit whose BL-D8 modules `readers` holds,
132
+ # and none without one, or with its controller away from its idle
133
+ # loop.
92
134
  def export(machine, readers)
93
135
  drive = readers["BL-D8"]
94
136
  model = drive&.text("model", "")
95
- return [] unless TYPES.value?(model) && idle?(readers)
137
+ return [] unless WORDS.key?(model) && idle?(model, readers)
96
138
 
97
- [unit(model, readers.fetch("BL-D8-MECH")), *Drive2031::UNITS.drop(1).map { |name| none(name) },
139
+ [unit(model, readers), *Drive2031::UNITS.drop(1).map { |name| none(name) },
98
140
  Drive2031.cpu_section(machine, readers.fetch("BL-D8-IPCPU"), ram(readers)),
99
141
  riot(RIOTS[0], readers.fetch("BL-D8-RIOT1")), riot(RIOTS[1], readers.fetch("BL-D8-RIOT2")),
100
- fdc(readers.fetch("BL-D8-RRIOT"))]
142
+ fdc(model, readers)]
101
143
  end
102
144
 
103
145
  # Whether both processors are between instructions, the
104
146
  # 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
147
+ def idle?(model, readers)
148
+ controller = readers.fetch(hard_disk?(model) ? "BL-D8-SASICPU" : "BL-D8-FDCCPU")
149
+ !position?(controller) && !position?(readers.fetch("BL-D8-IPCPU")) &&
150
+ controller.int("pc", 0) == controller_loop(model)
151
+ end
152
+
153
+ # Where the model's controller, or its SASI host, waits for jobs.
154
+ def controller_loop(model)
155
+ case model
156
+ when "9060", "9090" then Drives::Model9060::Idle::SASI_LOOP
157
+ when "2040" then Drives::Model2040::FDC_LOOP
158
+ when "4040", "3040" then Drives::Model4040::FDC_LOOP
159
+ else Drives::Model8050::Idle::FDC_LOOP
160
+ end
109
161
  end
110
162
 
111
163
  def position?(cpu) = !cpu.text("pos.seq", "").empty?
112
164
 
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
165
+ def unit(model, readers)
166
+ count = DRIVE_COUNTS.fetch(model)
167
+ bytes = Array.new(size(model), 0)
168
+ IDLE.each { |offset, value| bytes[offset] = value }
169
+ IDLE_SECOND.each { |offset, value| bytes[BLOCK + offset] = value } if count > 1
170
+ bytes[COUNT] = count
171
+ tracks = half_tracks(model, readers)
172
+ count.times do |drive|
173
+ at = BLOCK * drive
174
+ bytes[at + HALF_TRACK] = tracks[drive]
175
+ bytes[at + TYPE, 2] = [WORDS.fetch(model)].pack("v").bytes
119
176
  end
120
177
  FieldWriter.new.bytes(bytes).section(UNIT, Drive2031::DRIVE_MAJOR, Drive2031::DRIVE_MINOR)
121
178
  end
122
179
 
180
+ # Each drive's head as xpet's half track: the 8050's cylinder on
181
+ # from 1, the 4040's half track on from track 1, and a hard
182
+ # disk's where xpet keeps it.
183
+ def half_tracks(model, readers)
184
+ return [HARD_DISK_TRACK] if hard_disk?(model)
185
+
186
+ mechanism = readers.fetch("BL-D8-MECH")
187
+ if DOS2_BOARD.include?(model)
188
+ mechanism.ints("half_tracks", [0, 0]).map { |half_track| half_track + 2 }
189
+ else
190
+ mechanism.ints("cylinders", [0, 0]).map { |cylinder| (cylinder + 1) * 2 }
191
+ end
192
+ end
193
+
123
194
  def none(name) = Drive2031.unit(Drive2031::NONE, name)
124
195
 
125
196
  # The IP's RAM as xpet lays it out: page 0 from both 6532s, then
@@ -143,16 +214,25 @@ module Badline
143
214
  fields.zeros(2).flag(input.bool("timer.irq_enabled", false)).section(name, 0, 0)
144
215
  end
145
216
 
146
- def fdc(rriot)
217
+ # FDC0 with the controller's track, and a hard disk's with none.
218
+ def fdc(model, readers)
147
219
  bytes = FDC_IDLE.dup
148
- bytes[FDC_TRACK] = rriot.bytes("ram", "".b).getbyte(TRACK_AT).to_i
220
+ unless hard_disk?(model)
221
+ ram = readers.fetch("BL-D8-RRIOT").bytes("ram", "".b)
222
+ bytes[FDC_TRACK] = if DOS2_BOARD.include?(model)
223
+ ram.getbyte(DOS2_TRACK_AT).to_i & DOS2_TRACK
224
+ else
225
+ ram.getbyte(TRACK_AT).to_i
226
+ end
227
+ end
149
228
  FieldWriter.new.bytes(bytes).section(FDC, 0, 0)
150
229
  end
151
230
 
152
231
  # The unit's keyed modules, named for device 8, from the
153
232
  # snapshot's: the IP's side from xpet's, the controller's side
154
233
  # from `reference`, a unit's in its idle loop, and `disks`, the
155
- # drives' disk modules badline keeps.
234
+ # drives' disk modules badline keeps, or a hard disk's controller
235
+ # with its image.
156
236
  def import(container, reference, disks)
157
237
  cpu = container[CPU]
158
238
  page = FieldReader.new(cpu).skip(Drive2031::RAM_AT).string(ZERO_PAGE * 2)
@@ -162,16 +242,48 @@ module Badline
162
242
  riot_section("BL-D8-RIOT1", container[RIOTS[0]], page.byteslice(0, ZERO_PAGE)),
163
243
  riot_section("BL-D8-RIOT2", container[RIOTS[1]], page.byteslice(ZERO_PAGE, ZERO_PAGE)),
164
244
  Keyed.section("BL-D8-IPCPU") { |out| Drive2031.registers(cpu, out) }]
165
- sections + reference.select { |section| CONTROLLER.include?(section.name) } + disks
245
+ sections + controller_side(reference, disks)
166
246
  end
167
247
 
168
- # The controller's side, which comes from a unit in its idle loop.
248
+ # The controller's side, which comes from a unit in its idle loop:
249
+ # a floppy unit's, and a hard disk's SASI host.
169
250
  CONTROLLER = %w[BL-D8-FDCCPU BL-D8-VIA BL-D8-RRIOT BL-D8-MECH].freeze
251
+ HOST = %w[BL-D8-SASICPU BL-D8-SASIRAM BL-D8-VIA].freeze
252
+
253
+ def controller_side(reference, disks)
254
+ base = reference.find { |section| section.name == SASI }
255
+ return reference.select { |section| CONTROLLER.include?(section.name) } + disks if base.nil?
256
+
257
+ reference.select { |section| HOST.include?(section.name) } + [controller(base, disks)]
258
+ end
259
+
260
+ # The hard disk's controller as `base` has it, with the image
261
+ # badline's kept in `disks`.
262
+ def controller(base, disks)
263
+ kept = disks.find { |section| section.name == SASI }
264
+ return base if kept.nil?
170
265
 
266
+ target = Drives::Model9060::SASITarget.new
267
+ target.load_fields(Keyed::Reader.parse(base))
268
+ own = Keyed::Reader.parse(kept)
269
+ path = own.text("path", "")
270
+ if File.file?(path)
271
+ target.disk = Storage::D90Image.new(path, read_only: own.bool("read_only", false),
272
+ heads: own.int("disk_heads", 4))
273
+ end
274
+ Keyed.section(SASI) { |out| target.save_fields(out) }
275
+ end
276
+
277
+ # The unit's own fields: its model and clock, with the controller's
278
+ # last byte from `reference`, or a hard disk's SASI lines.
171
279
  def unit_fields(container, reference, out)
172
280
  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))
281
+ model = own.text("model", "8050")
282
+ out.text("model", model).int("phase", 0).int("cycles", FieldReader.new(container[CPU]).qword)
283
+ return out.int("fdc_data", own.int("fdc_data", 0)) unless hard_disk?(model)
284
+
285
+ out.int("sasi_data", own.int("sasi_data", 0)).bool("sasi.ack_high", own.bool("sasi.ack_high", true))
286
+ out.bool("sasi.reset_high", own.bool("sasi.reset_high", false))
175
287
  end
176
288
 
177
289
  def riot_section(name, section, ram)
@@ -199,15 +311,23 @@ module Badline
199
311
  end
200
312
 
201
313
  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)
314
+ unit = new_unit(model)
315
+ unit.boot_cycles.times { unit.host_cycle! }
316
+ unit.cycle! until unit.settled_for_vice?
205
317
  unit.save_modules([], "BL-D8")
206
318
  end
207
319
 
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
320
+ def new_unit(model)
321
+ case model
322
+ when "8250" then Drives::Model8250.new
323
+ when "1001" then Drives::Model1001.new
324
+ when "4040" then Drives::Model4040.new
325
+ when "3040" then Drives::Model3040.new
326
+ when "2040" then Drives::Model2040.new
327
+ when "9060" then Drives::Model9060.new
328
+ when "9090" then Drives::Model9090.new
329
+ else Drives::Model8050.new
330
+ end
211
331
  end
212
332
  end
213
333
  end