badline 0.16.0 → 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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +32 -9
  4. data/doc/media.md +9 -0
  5. data/doc/snapshot-format.md +4 -2
  6. data/doc/snapshots.md +51 -10
  7. data/lib/badline/drives/ieee_bus.rb +50 -4
  8. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  9. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  10. data/lib/badline/drives/model2031/parts.rb +25 -0
  11. data/lib/badline/drives/model2031.rb +92 -0
  12. data/lib/badline/frontend/menu/menu_pages.rb +11 -2
  13. data/lib/badline/kernal_trap/ieee.rb +3 -0
  14. data/lib/badline/machine.rb +3 -2
  15. data/lib/badline/media/pet_media.rb +5 -5
  16. data/lib/badline/media/true_drive.rb +19 -6
  17. data/lib/badline/media.rb +6 -2
  18. data/lib/badline/options/help.rb +8 -4
  19. data/lib/badline/options/table.rb +9 -3
  20. data/lib/badline/options/validation.rb +20 -4
  21. data/lib/badline/options.rb +27 -8
  22. data/lib/badline/pet/bus/expansion.rb +99 -5
  23. data/lib/badline/pet/bus.rb +30 -12
  24. data/lib/badline/pet/characters.rb +8 -2
  25. data/lib/badline/pet/crtc_video.rb +20 -7
  26. data/lib/badline/pet/ieee_drive.rb +76 -0
  27. data/lib/badline/pet/ieee_ports.rb +12 -1
  28. data/lib/badline/pet/ieee_traps.rb +10 -5
  29. data/lib/badline/pet/keyed_modules.rb +4 -1
  30. data/lib/badline/pet/keyed_parts.rb +10 -6
  31. data/lib/badline/pet/model.rb +56 -6
  32. data/lib/badline/pet/rom_sets.rb +30 -6
  33. data/lib/badline/pet.rb +33 -15
  34. data/lib/badline/roms/README +33 -1
  35. data/lib/badline/roms/dos2031.rom +0 -0
  36. data/lib/badline/roms/pet/characters-din.rom +0 -0
  37. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  38. data/lib/badline/snapshot/container.rb +4 -1
  39. data/lib/badline/snapshot/field_doc.rb +4 -3
  40. data/lib/badline/snapshot/image.rb +22 -11
  41. data/lib/badline/snapshot/pet_setup.rb +30 -13
  42. data/lib/badline/snapshot/vice/pet.rb +117 -0
  43. data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
  44. data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
  45. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  46. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  47. data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
  48. data/lib/badline/snapshot/vice/pet_via.rb +164 -0
  49. data/lib/badline/snapshot/vice.rb +1 -0
  50. data/lib/badline/version.rb +1 -1
  51. metadata +16 -1
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/drives/model2031"
4
+
5
+ module Badline
6
+ class PET
7
+ # The PET's true drive: a Drives::Model2031 on the IEEE-488 bus
8
+ # (IEEEPorts), clocked after the CPU each cycle and reset with it, as
9
+ # the PET's RES line reaches IFC. The traps on the KERNAL's primitives
10
+ # (IEEETraps) leave device 8 to the drive while it is device 8.
11
+ module IEEEDrive
12
+ # The 2031 on the bus, or nil.
13
+ attr_reader :drive2031
14
+
15
+ # Plugs in a Drives::Model2031, in place of the one there.
16
+ def attach_drive2031(drive)
17
+ detach_drive2031
18
+ drive.host_clock_hz = 1_000_000
19
+ drive.connect(@ieee.bus)
20
+ @drive2031 = drive
21
+ @ieee.lines_written
22
+ @ieee_trap&.device = trap_device
23
+ @init_threshold = [@init_threshold, Badline::Drives::Model2031::BOOT_CYCLES].max if @cycles < @init_threshold
24
+ end
25
+
26
+ # Unplugs the 2031, leaving it on a bus of its own.
27
+ def detach_drive2031
28
+ drive = @drive2031
29
+ return unless drive
30
+
31
+ drive.connect(Badline::Drives::IEEEBus.new)
32
+ @drive2031 = nil
33
+ @ieee_trap&.device = trap_device
34
+ end
35
+
36
+ # The true drive on the bus, or nil.
37
+ def true_drive = @drive2031
38
+
39
+ # Plugs in a 2031 as device 8, in place of the drive there, and
40
+ # returns it.
41
+ def plug_true_drive
42
+ Badline::Drives::Model2031.new.tap { |drive| attach_drive2031(drive) }
43
+ end
44
+
45
+ private
46
+
47
+ # The 2031 as keyed snapshot modules named from its device number:
48
+ # BL-D8, BL-D8-CPU and so on for device 8.
49
+ def save_drive_modules(sections)
50
+ drive = @drive2031
51
+ drive&.save_modules(sections, "BL-D#{drive.device}")
52
+ sections
53
+ end
54
+
55
+ # The 2031 a snapshot's modules hold, built afresh unless one is on
56
+ # the bus as the same device, or none when they hold no drive. The
57
+ # bus then carries the PET's lines as its chips left them.
58
+ def load_drive_modules(modules)
59
+ device = 8
60
+ input = modules.optional("BL-D8")
61
+ while input.nil? && device < 11
62
+ device += 1
63
+ input = modules.optional("BL-D#{device}")
64
+ end
65
+ if input
66
+ detach_drive2031 if @drive2031 && @drive2031.device != device
67
+ attach_drive2031(Badline::Drives::Model2031.new(device:)) unless @drive2031
68
+ @drive2031.load_modules(modules, input, "BL-D#{device}")
69
+ else
70
+ detach_drive2031
71
+ end
72
+ @ieee.lines_written
73
+ end
74
+ end
75
+ end
76
+ end
@@ -15,8 +15,12 @@ module Badline
15
15
  # The outputs go through inverting drivers onto the bus, so a 0 on a
16
16
  # data line's port bit pulls the line low, and the inputs read the
17
17
  # lines as they are, a 0 for a low line. It is PIA 2's peripheral, and
18
- # answers what PIA 1 and the VIA read of the bus.
18
+ # answers what PIA 1 and the VIA read of the bus. It pushes the lines
19
+ # the PET pulls into the bus for the drives on it after every write to
20
+ # the chips (lines_written).
19
21
  class IEEEPorts
22
+ attr_reader :bus
23
+
20
24
  def initialize(bus)
21
25
  @bus = bus
22
26
  @pia1 = @pia2 = @via = nil
@@ -46,6 +50,13 @@ module Badline
46
50
  value
47
51
  end
48
52
 
53
+ # The PET wrote one of its chips: the drives on the bus see the lines
54
+ # it pulls now.
55
+ def lines_written
56
+ bus = @bus
57
+ bus.host_lines = controller_lines unless bus.drives.empty?
58
+ end
59
+
49
60
  private
50
61
 
51
62
  def low_lines = @bus.low_lines(controller_lines)
@@ -8,12 +8,15 @@ module Badline
8
8
  # commands, run through the primitives.
9
9
  module IEEETraps
10
10
  # Puts the storage in device 8. Mounting again swaps the disk while
11
- # the machine runs, and the drive keeps its RAM and its status.
11
+ # the machine runs, and the drive keeps its RAM and its status. The
12
+ # traps leave device 8 to a true drive with that number.
12
13
  def mount(storage)
13
14
  return @dos.insert(storage) if @dos
14
15
 
15
16
  @dos = dos = KernalTrap::DOS.new(storage, channel_saves: true)
16
17
  @ieee_trap = KernalTrap::IEEE.new(cpu:, bus:, layout: trap_layout, dos:).install
18
+ @ieee_trap.device = trap_device
19
+ @ieee_trap
17
20
  end
18
21
 
19
22
  # Takes device 8's storage out, and the traps with it, so the KERNAL
@@ -33,10 +36,6 @@ module Badline
33
36
  # The path of the disk or directory device 8 serves, or an empty one.
34
37
  def mounted_path = @dos.nil? ? "" : @dos.path
35
38
 
36
- # The true drive on the bus, which a PET has none of yet: every disk
37
- # goes in device 8 through the traps.
38
- def true_drive = nil
39
-
40
39
  # Device 8 through the traps in a keyed snapshot module (BL-TRAPDOS):
41
40
  # the storage under storage.*, the DOS, and the IEEE-488 trap under
42
41
  # ieee.*. Nothing mounted writes nothing.
@@ -70,6 +69,12 @@ module Badline
70
69
 
71
70
  # The IEEELayout of the KERNAL the model runs.
72
71
  def trap_layout = @model.trap_layout
72
+
73
+ # Device 8, unless a true drive is device 8.
74
+ def trap_device
75
+ device = KernalTrap::Routine::DEVICE
76
+ true_drive&.device == device ? nil : device
77
+ end
73
78
  end
74
79
  end
75
80
  end
@@ -7,7 +7,8 @@ module Badline
7
7
  # the screen RAM, the last byte on the bus and an 8096's board, both
8
8
  # PIAs, the VIA, the CRTC where the model has one, the picture with
9
9
  # the video's beam, the CPU, the datasettes at cassette ports #1 and
10
- # #2, and device 8 through the traps.
10
+ # #2, a 2031 on the IEEE-488 bus (IEEEDrive), and device 8 through the
11
+ # traps.
11
12
  #
12
13
  # A load lays the pages out before the chips load, and works out what
13
14
  # each unit derives from its fields once every unit is loaded.
@@ -24,6 +25,7 @@ module Badline
24
25
  cassettes = @cassettes
25
26
  sections << Snapshot::Keyed.section("BL-TAPE1") { |out| cassettes.datasette1.save_fields(out) }
26
27
  sections << Snapshot::Keyed.section("BL-TAPE2") { |out| cassettes.datasette2.save_fields(out) }
28
+ save_drive_modules(sections)
27
29
  sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
28
30
  sections
29
31
  end
@@ -43,6 +45,7 @@ module Badline
43
45
  @cpu.load_fields(modules.required("BL-CPU"))
44
46
  @cassettes.datasette1.load_fields(modules.required("BL-TAPE1"))
45
47
  @cassettes.datasette2.load_fields(modules.required("BL-TAPE2"))
48
+ load_drive_modules(modules)
46
49
  trap_dos = modules.optional("BL-TRAPDOS")
47
50
  trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
48
51
  @cpu.restored!
@@ -80,26 +80,30 @@ module Badline
80
80
 
81
81
  class Bus
82
82
  class Expansion
83
- # The control register and the board's 64K, under `prefix`. The
84
- # banks are laid over the pages again once the bus is loaded.
83
+ # The control register and the board's 64K, under `prefix`, and
84
+ # on the 8296 the user port lines its RAM last followed. The banks
85
+ # are laid over the pages again once the bus is loaded.
85
86
  module KeyedState
86
87
  def save_fields(out, prefix)
87
88
  out.int("#{prefix}control", @control).bytes("#{prefix}ram", @cells)
89
+ out.int("#{prefix}user_port", @lines) if @board8296
88
90
  end
89
91
 
90
92
  def load_fields(input, prefix)
91
93
  @control = input.byte("#{prefix}control", @control)
92
94
  input.bytes_into("#{prefix}ram", @cells)
95
+ @lines = input.int("#{prefix}user_port", USER_PORT_LINES) & USER_PORT_LINES
93
96
  end
94
97
  end
95
98
 
96
99
  include KeyedState
97
100
  end
98
101
 
99
- # The RAM below $8000, the screen RAM, the last byte on the bus under
100
- # bus.* and an 8096's board under expansion.*, in a keyed snapshot
101
- # module (BL-RAM). The pages are laid out again once they are loaded
102
- # (#restored!). The ROMs come with the model.
102
+ # The RAM below $8000, the screen RAM, all 32K from $8000 on an 8296,
103
+ # the last byte on the bus under bus.* and an 8096's or 8296's board
104
+ # under expansion.*, in a keyed snapshot module (BL-RAM). The pages
105
+ # are laid out again once they are loaded (#restored!). The ROMs come
106
+ # with the model.
103
107
  module KeyedState
104
108
  def save_fields(out)
105
109
  @ram.save_fields(out, "ram")
@@ -3,8 +3,8 @@
3
3
  module Badline
4
4
  class PET
5
5
  # The PETs and CBMs badline builds by name, after xpet's -model names:
6
- # the board's RAM, its stock ROM set and video, and whether the 8096's
7
- # 64K board is fitted.
6
+ # the board's RAM, its stock ROM set and video, and whether a 64K
7
+ # board banked through $FFF0 is fitted, the 8096's or the 8296's own.
8
8
  #
9
9
  # +video+ is :discrete, the fixed 40-column, 60 Hz generator of the
10
10
  # 2001 and 3000 series, :crtc40 or :crtc80, a 6545 the editor ROM
@@ -14,7 +14,10 @@ module Badline
14
14
  #
15
15
  # The 2001's 1K of screen RAM repeats four times through $8000-$8FFF.
16
16
  # Later boards decode $8000-$87FF alone, the 40-column ones with their
17
- # 1K twice over and the 80-column ones with 2K.
17
+ # 1K twice over and the 80-column ones with 2K. The 8296 has 128K of
18
+ # dynamic RAM, the 32K below $8000, the 32K above it, of which the
19
+ # screen is the start and the rest lies under the ROMs, and the 64K
20
+ # board (Bus::Expansion).
18
21
  module Model
19
22
  Profile = Data.define(:name, :ram_kb, :basic, :editor_50hz, :editor_60hz, :kernal, :characters, :keyboard,
20
23
  :video, :expansion) do
@@ -22,10 +25,18 @@ module Badline
22
25
 
23
26
  def editor(refresh_hz) = refresh_hz == 50 ? editor_50hz : editor_60hz
24
27
 
25
- def screen_bytes = video == :crtc80 ? 0x800 : 0x400
28
+ # The 8296, whose board decodes its memory in a PLA.
29
+ def cbm8296? = name == "8296"
30
+
31
+ # The screen RAM's bytes, and on the 8296 all 32K of RAM from $8000.
32
+ def screen_bytes
33
+ return 0x8000 if cbm8296?
34
+
35
+ video == :crtc80 ? 0x800 : 0x400
36
+ end
26
37
 
27
38
  # How many pages from $8000 on the screen RAM answers in.
28
- def screen_pages = name == "2001" ? 0x10 : 0x08
39
+ def screen_pages = name == "2001" || cbm8296? ? 0x10 : 0x08
29
40
 
30
41
  # The BASIC 1 KERNAL keeps its keyboard buffer and jiffy clock in
31
42
  # page 2; BASIC 2 and 4 keep them where each other does.
@@ -70,7 +81,12 @@ module Badline
70
81
  kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
71
82
  video: :crtc80, expansion: true)
72
83
 
73
- ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096].freeze
84
+ CBM8296 = Profile.new(name: "8296", ram_kb: 32, basic: "pet/basic4.rom",
85
+ editor_50hz: "pet/editor4-80-b-50hz-8296.rom", editor_60hz: "pet/editor4-80-b-60hz.rom",
86
+ kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
87
+ video: :crtc80, expansion: true)
88
+
89
+ ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096, CBM8296].freeze
74
90
 
75
91
  DEFAULT = "8032"
76
92
 
@@ -85,6 +101,40 @@ module Badline
85
101
  model
86
102
  end
87
103
 
104
+ # Whether the +profile+'s board takes +ram_kb+ below $8000: the 8296
105
+ # has its 32K soldered on.
106
+ def self.ram_fits?(profile, ram_kb)
107
+ profile.cbm8296? ? ram_kb == 32 : RAM_SIZES.include?(ram_kb)
108
+ end
109
+
110
+ # The +jumpers+ the +profile+'s board has fitted, which only the
111
+ # 8296's takes (ROMSets::JUMPERS), in that order. Raises
112
+ # ArgumentError for any it doesn't have.
113
+ def self.checked_jumpers(profile, jumpers)
114
+ known = ROMSets.jumpers(profile.name)
115
+ unknown = jumpers.reject { |jumper| known.include?(jumper) }
116
+ raise ArgumentError, "the #{profile.name} has no jumper #{unknown.first}" unless unknown.empty?
117
+
118
+ known.select { |jumper| jumpers.include?(jumper) }
119
+ end
120
+
121
+ # The +profile+ with the character ROM ROMSets::CHARACTERS names in
122
+ # place of its own, nil for its own. Raises ArgumentError for one the
123
+ # model doesn't take.
124
+ def self.fitted(profile, characters)
125
+ return profile if characters.nil?
126
+
127
+ name = profile.name
128
+ unless ROMSets.characters(name).include?(characters)
129
+ raise ArgumentError, "the #{name} takes no character ROM #{characters}"
130
+ end
131
+
132
+ Profile.new(name:, ram_kb: profile.ram_kb, basic: profile.basic, editor_50hz: profile.editor_50hz,
133
+ editor_60hz: profile.editor_60hz, kernal: profile.kernal,
134
+ characters: ROMSets::CHARACTERS.fetch(characters), keyboard: profile.keyboard,
135
+ video: profile.video, expansion: profile.expansion)
136
+ end
137
+
88
138
  # The +profile+ with the BASIC and the editor ROMSets names in place
89
139
  # of its own: +basic+ a key of ROMSets::BASICS and +editor+ one of
90
140
  # ROMSets::EDITORS, each nil for the one that fits. An editor named
@@ -4,8 +4,9 @@ module Badline
4
4
  class PET
5
5
  # The BASIC and editor ROMs each model's board takes: its own, and the
6
6
  # upgrades Commodore sold for it, BASIC 2 for the 2001 and BASIC 4 for
7
- # the 3032s. Options reads it to check --basic and --editor, so it
8
- # loads nothing else.
7
+ # the 3032s, and the 8296's character ROMs and board jumpers. Options
8
+ # reads it to check --basic, --editor, --characters and --jumpers, so
9
+ # it loads nothing else.
9
10
  #
10
11
  # An editor ROM goes with one BASIC, scans one keyboard and drives one
11
12
  # board's video, so a model and a BASIC fit one editor, and on a CRTC
@@ -23,26 +24,42 @@ module Badline
23
24
  "1" => "pet/editor1.rom", "2-n" => "pet/editor2-n.rom", "2-b" => "pet/editor2-b.rom",
24
25
  "4-n" => "pet/editor4-n.rom", "4-b" => "pet/editor4-b.rom", "4-40-n-50hz" => "pet/editor4-40-n-50hz.rom",
25
26
  "4-40-n-60hz" => "pet/editor4-40-n-60hz.rom", "4-80-b-50hz" => "pet/editor4-80-b-50hz.rom",
26
- "4-80-b-50hz-0283" => "pet/editor4-80-b-50hz-0283.rom", "4-80-b-60hz" => "pet/editor4-80-b-60hz.rom"
27
+ "4-80-b-50hz-0283" => "pet/editor4-80-b-50hz-0283.rom", "4-80-b-60hz" => "pet/editor4-80-b-60hz.rom",
28
+ "4-80-b-50hz-8296" => "pet/editor4-80-b-50hz-8296.rom"
27
29
  }.freeze
28
30
 
29
31
  # The BASICs each model takes, its own first, and the editors that
30
32
  # fit each, the 50 Hz one first and the 60 Hz one last on a CRTC
31
33
  # board. The 8032's other 50 Hz editor, 901474-04 dated 0283, sits
32
- # between them.
34
+ # between them, and the 8296 takes the 8032's as well as its own.
33
35
  FITS = {
34
36
  "2001" => { "1" => %w[1], "2" => %w[2-n] },
35
37
  "3032" => { "2" => %w[2-n], "4" => %w[4-n] },
36
38
  "3032b" => { "2" => %w[2-b], "4" => %w[4-b] },
37
39
  "4032" => { "4" => %w[4-40-n-50hz 4-40-n-60hz] },
38
40
  "8032" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
39
- "8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
41
+ "8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
42
+ "8296" => { "4" => %w[4-80-b-50hz-8296 4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
40
43
  }.freeze
41
44
 
45
+ # The character ROMs by the names --characters takes, and the ones
46
+ # each model's board takes, its own first: the 8296's socket takes
47
+ # 901447-10, its ASCII one, or the 4K 324242-01, which holds a DIN set
48
+ # in its first half and 901447-10 in its second.
49
+ CHARACTERS = { "ascii" => "pet/characters2.rom", "din" => "pet/characters-din.rom" }.freeze
50
+
51
+ CHARACTER_FITS = { "8296" => %w[ascii din] }.freeze
52
+
53
+ # The jumpers each model's board takes, by the names --jumpers takes
54
+ # (Bus::Expansion), none on a board not named.
55
+ JUMPERS = { "8296" => %w[ram9 rama userport] }.freeze
56
+
42
57
  # The cycles a line and the lines a frame the CRTC editors set up
43
58
  # where they differ from 50-cycle lines, 400 a frame at 50 Hz and 333
44
59
  # at 60 Hz, by the editor's ROM.
45
- CRTC_FRAMES = { "pet/editor4-80-b-50hz-0283.rom" => [64, 313] }.freeze
60
+ CRTC_FRAMES = {
61
+ "pet/editor4-80-b-50hz-0283.rom" => [64, 313], "pet/editor4-80-b-50hz-8296.rom" => [59, 339]
62
+ }.freeze
46
63
 
47
64
  # The cycles a line and the lines a frame the CRTC editor in the ROM
48
65
  # named sets up at +refresh_hz+.
@@ -55,6 +72,13 @@ module Badline
55
72
  # a BASIC it doesn't take.
56
73
  def self.editors(model, basic) = FITS.fetch(model).fetch(basic, [])
57
74
 
75
+ # The character ROMs the model takes, by name, none for one that takes
76
+ # only its own.
77
+ def self.characters(model) = CHARACTER_FITS.fetch(model, [])
78
+
79
+ # The jumpers the model's board takes.
80
+ def self.jumpers(model) = JUMPERS.fetch(model, [])
81
+
58
82
  # The editor that fits the model with the BASIC at +refresh_hz+.
59
83
  def self.editor(model, basic, refresh_hz)
60
84
  fitting = editors(model, basic)
data/lib/badline/pet.rb CHANGED
@@ -8,6 +8,7 @@ require "badline/pet/via_ports"
8
8
  require "badline/pet/keyboard_ports"
9
9
  require "badline/pet/ieee_ports"
10
10
  require "badline/pet/ieee_traps"
11
+ require "badline/pet/ieee_drive"
11
12
  require "badline/pet/cassettes"
12
13
  require "badline/pet/characters"
13
14
  require "badline/pet/canvas"
@@ -42,13 +43,15 @@ module Badline
42
43
  # The VIA's CB2 is the sound (Sound).
43
44
  #
44
45
  # Device 8 answers through traps on the KERNAL's IEEE-488 primitives
45
- # (IEEETraps), on a bus nothing else is plugged into (IEEEPorts), and
46
- # each cassette port has a datasette (Cassettes). It runs headless.
46
+ # (IEEETraps), on the IEEE-488 bus (IEEEPorts), or a true 2031 on the
47
+ # bus answers (IEEEDrive), and each cassette port has a datasette
48
+ # (Cassettes). It runs headless.
47
49
  class PET
48
50
  include Machine::KeyboardBuffer
49
51
  include Machine::Clocking
50
52
  include Machine::Settling
51
53
  include IEEETraps
54
+ include IEEEDrive
52
55
 
53
56
  attr_reader :model, :bus, :cpu, :pia1, :pia2, :via, :crtc, :keyboard, :ieee, :cassettes, :cycles, :init_threshold
54
57
 
@@ -58,6 +61,9 @@ module Badline
58
61
  # The RAM below $8000, in K.
59
62
  attr_reader :ram_kb
60
63
 
64
+ # The 8296's board jumpers that are fitted, by name.
65
+ attr_reader :jumpers
66
+
61
67
  def ram = @bus.ram
62
68
 
63
69
  # Reads and writes memory as the CPU sees it, through the bus.
@@ -75,14 +81,17 @@ module Badline
75
81
  # +model+ names one of Model::ALL, with +ram_kb+ of RAM below $8000
76
82
  # (Model::RAM_SIZES) in place of the model's own when not nil. A CRTC
77
83
  # model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
78
- # +basic+ and +editor+ name ROMs the board takes in place of its own
79
- # (Model.upgraded).
80
- def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil)
81
- @model = Model.upgraded(Model.named(model), basic, editor)
84
+ # +basic+, +editor+ and +characters+ name ROMs the board takes in place
85
+ # of its own (Model.upgraded, Model.fitted), and +jumpers+ the 8296's
86
+ # board jumpers (ROMSets::JUMPERS).
87
+ def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil,
88
+ characters: nil, jumpers: [])
89
+ @model = Model.fitted(Model.upgraded(Model.named(model), basic, editor), characters)
82
90
  ram_kb ||= @model.ram_kb
83
- raise ArgumentError, "a PET has no #{ram_kb}K RAM board" unless Model::RAM_SIZES.include?(ram_kb)
91
+ raise ArgumentError, "the #{@model.name} has no #{ram_kb}K RAM board" unless Model.ram_fits?(@model, ram_kb)
84
92
 
85
93
  @ram_kb = ram_kb
94
+ @jumpers = Model.checked_jumpers(@model, jumpers)
86
95
 
87
96
  @refresh_hz = @model.crtc? ? refresh_hz : 60
88
97
  @crtc_video = @model.crtc?
@@ -99,16 +108,21 @@ module Badline
99
108
  @cassettes = Cassettes.new(@pia1, @via)
100
109
  keyboard_ports.connect(@pia1, @ieee, @cassettes)
101
110
  characters = Characters.new(@model.characters)
102
- @crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80)
111
+ @crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80,
112
+ board8296: @model.cbm8296?)
103
113
  @crtc = Chips::CRTC.new(@crtc_output, registers:)
104
114
  @ieee.connect(@pia1, @pia2, @via)
105
115
  @bus = Bus.new(model: @model, ram_kb:, editor: @model.editor(@refresh_hz), pia1: @pia1, pia2: @pia2,
106
- via: @via, crtc: @crtc)
116
+ via: @via, crtc: @crtc, jumpers: @jumpers)
107
117
  @crtc_output.cells = @bus.screen.cells
108
118
  @discrete = DiscreteVideo.new(@bus.screen.cells, characters, @via)
109
119
  @canvas = @crtc_video ? @crtc_output.canvas : @discrete.canvas
110
120
  cassettes = @cassettes
111
- @bus.on_io_write { cassettes.lines_written }
121
+ ieee = @ieee
122
+ @bus.on_io_write do
123
+ cassettes.lines_written
124
+ ieee.lines_written
125
+ end
112
126
  @cpu = CPU.new(@bus, debug:)
113
127
  @cycles = 0
114
128
  @init_handlers = []
@@ -116,6 +130,7 @@ module Badline
116
130
  @dos = nil
117
131
  @ieee_trap = nil
118
132
  @capture_output = nil
133
+ @drive2031 = nil
119
134
  @init_threshold = boot_cycles(ram_kb)
120
135
  end
121
136
 
@@ -123,8 +138,8 @@ module Badline
123
138
  def vertical_drive? = @vertical_drive
124
139
 
125
140
  # The model, its RAM and its editor's frame rate as the pause menu
126
- # names them, such as "8032, 32K RAM, 50 HZ".
127
- def model_label = "#{@model.name.upcase}, #{@ram_kb}K RAM, #{@refresh_hz} HZ"
141
+ # names them, such as "8032, 32K RAM, 50 HZ", all 128K on an 8296.
142
+ def model_label = "#{@model.name.upcase}, #{@model.cbm8296? ? 128 : @ram_kb}K RAM, #{@refresh_hz} HZ"
128
143
 
129
144
  # The Canvas a front end shows.
130
145
  def video = @canvas
@@ -146,7 +161,7 @@ module Badline
146
161
  end
147
162
 
148
163
  # The video counts and moves the vertical drive line, then the PIAs
149
- # and the VIA clock, ahead of the CPU.
164
+ # and the VIA clock, ahead of the CPU, and the drive after it.
150
165
  def cycle!
151
166
  handle_init if @cycles == @init_threshold
152
167
  feed_keyboard if @pending_keys
@@ -165,6 +180,7 @@ module Badline
165
180
  @cassettes.cycle!
166
181
  @cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
167
182
  @cpu.cycle!
183
+ @drive2031&.host_cycle!
168
184
 
169
185
  @cycles += 1
170
186
  end
@@ -173,14 +189,16 @@ module Badline
173
189
  # and #drain_samples.
174
190
  def sound_source = @sound
175
191
 
176
- # The RES line reaches the CPU, the PIAs, the VIA, the CRTC and the
177
- # 8096's control register.
192
+ # The RES line reaches the CPU, the PIAs, the VIA, the CRTC, the
193
+ # 8096's control register and, as IFC, the drive.
178
194
  def reset!
179
195
  @pia1.reset!
180
196
  @pia2.reset!
181
197
  @via.reset!
182
198
  @crtc.reset!
183
199
  @bus.reset!
200
+ @drive2031&.reset!
201
+ @ieee.lines_written
184
202
  @cpu.reset!
185
203
  end
186
204
 
@@ -88,6 +88,18 @@ Downloaded from
88
88
  https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1551/1551.318008-01.bin
89
89
  SHA-1 fae3c788ad9a6cc2dbdfbcf6c0264b2ca921d55e.
90
90
 
91
+ dos2031.rom
92
+ -----------
93
+ The 2031 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 901484-03
94
+ ($C000-$DFFF, SHA-1 54d608f7f07860f24186749f21c96724dd48bc50)
95
+ followed by 901484-05 ($E000-$FFFF, SHA-1
96
+ ec6b75ecfdd4744e5d57979ef6af990444c11ae1). Drives::Model2031 loads it.
97
+
98
+ Both halves downloaded from
99
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/2031/
100
+ and concatenated.
101
+ SHA-1 c53e180a96649ceb3f421739e8dc66faba7cba44.
102
+
91
103
  Commodore VIC-20 ROMs
92
104
  =====================
93
105
 
@@ -253,7 +265,9 @@ BASIC 4 for $B000-$DFFF, 901465-23, 901465-20 and 901465-21
253
265
  (basic-4-b000.901465-23.bin, basic-4-c000.901465-20.bin,
254
266
  basic-4-d000.901465-21.bin) concatenated. 901465-23 is the patched
255
267
  $B000 chip that replaced 901465-19. The 8296's 324746-01 holds the same
256
- four chips.
268
+ four chips (8296/kernal-basic-324746-01.bin, SHA-1
269
+ e3e1431969bf317c885e47f3790e0bcbdf61fe77, $C000, $D000, $B000 and
270
+ $F000 in that order), so the 8296 runs this file and kernal4.rom.
257
271
  SHA-1 d70bec479e1e3a0d0908a4897066e9959bbc11c8.
258
272
 
259
273
  pet/kernal4.rom
@@ -316,3 +330,21 @@ pet/editor4-80-b-60hz.rom
316
330
  The 8032's 60 Hz editor, 901474-03 (edit-4-80-b-60Hz.901474-03.bin),
317
331
  for $E000-$E7FF.
318
332
  SHA-1 c605fa343fd77c73cbe1e0e9567e2f014f6e7e30.
333
+
334
+ pet/editor4-80-b-50hz-8296.rom
335
+ ------------------------------
336
+ The 8296's 50 Hz editor, 324243-02B, the EPROM marked "8296 UE8" in
337
+ Andre Fachat's working 8296 (8296/edit-50hz-324243-02b.bin), for
338
+ $E000-$E7FF. The 8296 parts list names it the ASCII board's. It is
339
+ 901474-04 with the CRTC's register tables at $E72A changed to lines of
340
+ 59 cycles and frames of 339 lines (8296/edit-50hz-324243-02b.txt).
341
+ SHA-1 b715db83fd26458dfd254bef5c4aae636753f7f5.
342
+
343
+ pet/characters-din.rom
344
+ ----------------------
345
+ The 8296's 4K character generator for the DIN board, 324242-01
346
+ (8296/characters-324242-01.bin), which --characters din fits. Its
347
+ second half is 901447-10 byte for byte, and its first a DIN set, with
348
+ the German letters and the section sign in place of @ [ \ ] and in
349
+ some of the graphics. The CRTC's MA13, bit 5 of R12, picks the half.
350
+ SHA-1 9616f7f18757cccefb702a945f954b644d5b17d1.
Binary file
@@ -38,6 +38,9 @@ module Badline
38
38
  PLUS4 = "PLUS4"
39
39
  # xpet names its machine PET, for every model.
40
40
  PET = "PET"
41
+ # The file format version each machine's VICE emulator reads, where
42
+ # it isn't FORMAT_MAJOR.0: xpet refuses any other than 1.0.
43
+ FORMAT_MAJORS = { PET => 1 }.freeze
41
44
  GZIP_MAGIC = "\x1f\x8b".b
42
45
 
43
46
  # The VICE version block, when VICE wrote the file: four version
@@ -67,7 +70,7 @@ module Badline
67
70
  def [](name) = @sections.find { |section| section.name == name }
68
71
 
69
72
  def to_s
70
- header = MAGIC + [FORMAT_MAJOR, FORMAT_MINOR].pack("CC") + padded(@machine)
73
+ header = MAGIC + [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINOR].pack("CC") + padded(@machine)
71
74
  header += VERSION_MAGIC + @vice_version if @vice_version
72
75
  header + @sections.map { |section| encode(section) }.join
73
76
  end
@@ -37,8 +37,8 @@ module Badline
37
37
  # The machines the doc is made from: each family at power-on, the C64
38
38
  # with each kind of expansion, cartridge and media, and a second SID,
39
39
  # the 264 series' models, its TCBM interface, cartridge, tape and
40
- # true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096
41
- # and a PET with a disk and tapes.
40
+ # true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096,
41
+ # an 8296 with its user port jumpered and a PET with a disk and tapes.
42
42
  def self.machines
43
43
  {
44
44
  "c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
@@ -50,7 +50,8 @@ module Badline
50
50
  Plus4.new(sid_card: :mos8580, sid_card_address: 0xfe80),
51
51
  Plus4.new(ram_expansion: :csory256),
52
52
  plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")],
53
- "pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"), pet_with_media]
53
+ "pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"),
54
+ PET.new(model: "8296", characters: "din", jumpers: %w[userport]), pet_with_media]
54
55
  }
55
56
  end
56
57