badline 0.16.1 → 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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +102 -12
  5. data/doc/media.md +5 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +376 -69
  8. data/doc/snapshots.md +148 -14
  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/model8050/disk.rb +163 -0
  46. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  47. data/lib/badline/drives/model8050/idle.rb +177 -0
  48. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  49. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  50. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  51. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  52. data/lib/badline/drives/model8050/parts.rb +37 -0
  53. data/lib/badline/drives/model8050.rb +243 -0
  54. data/lib/badline/frontend/app.rb +10 -3
  55. data/lib/badline/frontend/boot.rb +1 -1
  56. data/lib/badline/frontend/controls.rb +51 -15
  57. data/lib/badline/frontend/gamepads.rb +3 -3
  58. data/lib/badline/frontend/keys.rb +61 -2
  59. data/lib/badline/frontend/media_slots.rb +1 -1
  60. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  61. data/lib/badline/frontend/menu/menu_pages.rb +18 -4
  62. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  63. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  64. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  65. data/lib/badline/frontend/timeline.rb +19 -10
  66. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  67. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  68. data/lib/badline/kernal_trap/dos.rb +18 -5
  69. data/lib/badline/kernal_trap/ieee.rb +4 -2
  70. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  71. data/lib/badline/machine/ieee_drives.rb +194 -0
  72. data/lib/badline/machine/settling.rb +4 -0
  73. data/lib/badline/machine.rb +23 -6
  74. data/lib/badline/media/cbm2_media.rb +29 -0
  75. data/lib/badline/media/program.rb +6 -3
  76. data/lib/badline/media/true_drive.rb +38 -9
  77. data/lib/badline/media.rb +15 -6
  78. data/lib/badline/options/family.rb +15 -3
  79. data/lib/badline/options/help.rb +16 -2
  80. data/lib/badline/options/table.rb +18 -4
  81. data/lib/badline/options/validation.rb +27 -3
  82. data/lib/badline/options.rb +21 -7
  83. data/lib/badline/pet/cassettes.rb +22 -1
  84. data/lib/badline/pet/ieee_drive.rb +5 -64
  85. data/lib/badline/pet/ieee_ports.rb +7 -0
  86. data/lib/badline/pet.rb +15 -4
  87. data/lib/badline/plus4/acia.rb +4 -298
  88. data/lib/badline/plus4/keyed_parts.rb +0 -44
  89. data/lib/badline/plus4.rb +1 -0
  90. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  91. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  92. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  93. data/lib/badline/roms/README +111 -0
  94. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  95. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  96. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  97. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  98. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  99. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  100. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  101. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  102. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  103. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  104. data/lib/badline/snapshot/container.rb +7 -2
  105. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  106. data/lib/badline/snapshot/field_doc.rb +38 -3
  107. data/lib/badline/snapshot/image.rb +62 -9
  108. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  109. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  110. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  111. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  112. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  113. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  114. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  115. data/lib/badline/snapshot/vice/cias.rb +13 -9
  116. data/lib/badline/snapshot/vice/p500.rb +96 -0
  117. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  118. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  119. data/lib/badline/snapshot/vice/pet.rb +7 -3
  120. data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
  121. data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
  122. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  123. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  124. data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
  125. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  126. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  127. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  128. data/lib/badline/snapshot/vice.rb +3 -0
  129. data/lib/badline/snapshot.rb +11 -3
  130. data/lib/badline/storage/crt_file.rb +7 -1
  131. data/lib/badline/storage/d64_image.rb +7 -0
  132. data/lib/badline/storage/d80_image.rb +17 -1
  133. data/lib/badline/storage/d81_image.rb +14 -0
  134. data/lib/badline/storage/disk_image/bam.rb +18 -3
  135. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  136. data/lib/badline/storage/disk_image/writing.rb +35 -0
  137. data/lib/badline/storage/disk_image.rb +2 -0
  138. data/lib/badline/storage.rb +1 -0
  139. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  140. data/lib/badline/support/p500_checkpoint.rb +35 -0
  141. data/lib/badline/version.rb +1 -1
  142. data/lib/badline.rb +1 -0
  143. metadata +72 -2
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # The machine's own fields as the B series keeps them (CBM2::KeyedState),
7
+ # and the whole machine as keyed snapshot modules (Snapshot::Keyed), in
8
+ # the order a load applies them: BL-MACHINE with the setup, the RAM
9
+ # with the segment registers, the colour RAM, the VIC, the SID, the
10
+ # CIA, the ACIA, both TPIs, the CPU, device 8 through the traps and a
11
+ # true 2031 on the IEEE-488 bus.
12
+ #
13
+ # A load lays the pages out before the chips load, and works out what
14
+ # each unit derives from its fields, the VIC's view of memory among
15
+ # them, once every unit is loaded.
16
+ module KeyedModules
17
+ # The SID's deferred cycles and writes, run up to this cycle, and the
18
+ # VIC's sprite collisions latched up to the beam.
19
+ def settle_chips_for_snapshot
20
+ @sid.catch_up!
21
+ @vic.fold_collisions!
22
+ end
23
+
24
+ def save_modules
25
+ bus = @bus
26
+ sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
27
+ sections << Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) }
28
+ sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.save_fields(out) }
29
+ sections << Snapshot::Keyed.section("BL-COLORRAM") { |out| @color_ram.save_fields(out, "ram") }
30
+ sections << Snapshot::Keyed.section("BL-VIC") { |out| @vic.save_fields(out) }
31
+ sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
32
+ sections << Snapshot::Keyed.section("BL-CIA") { |out| @cia.save_fields(out) }
33
+ sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
34
+ sections << Snapshot::Keyed.section("BL-TPI1") { |out| @tpi1.save_fields(out) }
35
+ sections << Snapshot::Keyed.section("BL-TPI2") { |out| @tpi2.save_fields(out) }
36
+ sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
37
+ save_drive_modules(sections)
38
+ end
39
+
40
+ # Raises Snapshot::FormatError for the modules of a machine built
41
+ # another way.
42
+ def load_modules(modules)
43
+ check_snapshot_setup(Snapshot::CBM2Setup.read_fields(modules.machine))
44
+ load_machine_fields(modules.machine)
45
+ @bus.load_fields(modules.required("BL-RAM"))
46
+ @bus.restored!
47
+ @color_ram.load_fields(modules.required("BL-COLORRAM"), "ram")
48
+ @vic.load_fields(modules.required("BL-VIC"))
49
+ @sid.load_fields(modules.required("BL-SID"))
50
+ @cia.load_fields(modules.required("BL-CIA"))
51
+ @acia.load_fields(modules.required("BL-ACIA"))
52
+ @tpi1.load_fields(modules.required("BL-TPI1"))
53
+ @tpi2.load_fields(modules.required("BL-TPI2"))
54
+ @cpu.load_fields(modules.required("BL-CPU"))
55
+ trap_dos = modules.optional("BL-TRAPDOS")
56
+ trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
57
+ load_drive_modules(modules)
58
+ restored!
59
+ self
60
+ end
61
+
62
+ private
63
+
64
+ def restored!
65
+ @cpu.restored!
66
+ @vic.restored!
67
+ @sid.restored!
68
+ @cia.restored!
69
+ @acia.restored!
70
+ update_video_bus
71
+ end
72
+
73
+ def check_snapshot_setup(setup)
74
+ ours = Snapshot::CBM2Setup.of(self)
75
+ return if setup == ours
76
+
77
+ raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
78
+ end
79
+ end
80
+
81
+ include CBM2::KeyedState
82
+ include KeyedModules
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # The P500s badline builds by name: the 510 of xcbm5x0's -model, PAL
7
+ # with the 6569 and NTSC with the 6567R8, each with its own region's
8
+ # mains unless told otherwise. Both have 128K of RAM, segments 0 and 1,
9
+ # and the one KERNAL and BASIC.
10
+ module Model
11
+ Profile = Data.define(:name, :region) do
12
+ def ram_kb = RAM_SEGMENTS.length * 64
13
+ end
14
+
15
+ M510 = Profile.new(name: "510", region: Region::PAL)
16
+ M510_NTSC = Profile.new(name: "510ntsc", region: Region::NTSC)
17
+
18
+ ALL = [M510, M510_NTSC].freeze
19
+ DEFAULT = "510"
20
+
21
+ RAM_SEGMENTS = [0, 1].freeze
22
+
23
+ # 901234-02, the KERNAL xcbm5x0 runs, and its BASIC 128, 901236-02
24
+ # and 901235-02.
25
+ KERNALS = { "02" => "cbm2/p500-kernal.rom" }.freeze
26
+ BASIC = "cbm2/p500-basic.rom"
27
+
28
+ def self.named(name)
29
+ model = ALL.find { |candidate| candidate.name == name }
30
+ raise ArgumentError, "no P500 model named #{name}" if model.nil?
31
+
32
+ model
33
+ end
34
+
35
+ def self.include?(name) = ALL.any? { |model| model.name == name }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # Page $D8 of segment 15: the VIC-II's 64 registers, repeated over the
7
+ # page.
8
+ class VICPage
9
+ def initialize(vic)
10
+ @vic = vic
11
+ end
12
+
13
+ def peek(addr) = @vic.peek(0xd000 | (addr & 0x3f))
14
+
15
+ def poke(addr, value)
16
+ @vic.poke(0xd000 | (addr & 0x3f), value)
17
+ end
18
+ end
19
+
20
+ # The colour RAM's four bits at $D400-$D7FF of segment 15, which the
21
+ # CPU reads with 0 in the high four, as xcbm5x0 does.
22
+ class ColorPages
23
+ def initialize(color_ram)
24
+ @color_ram = color_ram
25
+ end
26
+
27
+ def peek(addr) = @color_ram.nibble(addr)
28
+
29
+ def poke(addr, value)
30
+ @color_ram.poke(addr, value)
31
+ end
32
+ end
33
+
34
+ # A TPI's page, $DE or $DF of segment 15. TPI1's CA and CB are STATVID
35
+ # and VICDOTSEL, and TPI2's PC6 and PC7 the VIC's bank lines, which
36
+ # an access to either chip can move, so the machine takes them again
37
+ # after each one (P500#update_video_bus).
38
+ class TPIPage
39
+ def initialize(tpi, machine)
40
+ @tpi = tpi
41
+ @machine = machine
42
+ end
43
+
44
+ def peek(addr)
45
+ value = @tpi.peek(addr)
46
+ @machine.update_video_bus
47
+ value
48
+ end
49
+
50
+ def poke(addr, value)
51
+ @tpi.poke(addr, value)
52
+ @machine.update_video_bus
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/cbm2/p500/model"
4
+ require "badline/cbm2/p500/pages"
5
+ require "badline/cbm2/p500/control_ports"
6
+ require "badline/cbm2/p500/ieee_layout"
7
+ require "badline/cbm2/p500/keyed_state"
8
+
9
+ module Badline
10
+ class CBM2
11
+ # The P500, the CBM 510: the B series' 6509, SID, CIA, ACIA and TPIs on
12
+ # the same segment bus (Bus, CPU), at the VIC-II's clock of about
13
+ # 1 MHz, with a VIC-II at $D800 in place of the 6845. RAM fills segments
14
+ # 0 and 1.
15
+ #
16
+ # Segment 15 holds what the B series' does but for $C000-$D7FF: the
17
+ # character ROM at $C000, which the CPU reads too, the 1K static video
18
+ # matrix at $D000 and the colour RAM's nibbles at $D400. What the VIC
19
+ # reads follows the P500's video bus (Chips::VIC::Bank#map_video_bus):
20
+ # TPI1's CA is STATVID and its CB VICDOTSEL, and TPI2's PC6 and PC7
21
+ # drive VA14 and VA15, which pick the 16K of segment 0 the VIC sees.
22
+ # BA halts the CPU as on the C64, and the VIC's IRQ reaches the CPU
23
+ # along with TPI1's.
24
+ #
25
+ # TPI1 is the interrupt controller as on the B series, with the mains
26
+ # on I0, the CIA on I2 and the ACIA on I4. TPI2 scans the B series'
27
+ # key matrix (KeyboardPorts), and the CIA reads the two control ports
28
+ # (ControlPorts), whose paddles reach the SID's pots and whose port 1
29
+ # fire line is the VIC's light pen input. Device 8 answers through traps on the KERNAL's
30
+ # IEEE-488 primitives as on the B series (IEEETraps, with the P500's
31
+ # IEEELayout), or a true 2031, 8050 or 8250 on the bus answers, on the
32
+ # same ports as the B series' (IEEEPorts, IEEEDrive).
33
+ class P500
34
+ include Machine::KeyboardBuffer
35
+ include Machine::Clocking
36
+ include Machine::Settling
37
+ include IEEETraps
38
+ include IEEEDrive
39
+
40
+ # The segment BASIC keeps its program text in.
41
+ TEXT_SEGMENT = 0
42
+
43
+ # The ACIA at $DD00, whose IRQ reaches TPI1's I4.
44
+ class ACIA < Chips::ACIA
45
+ end
46
+
47
+ attr_reader :model, :bus, :cpu, :vic, :sid, :cia, :acia, :tpi1, :tpi2, :keyboard, :joystick1, :joystick2,
48
+ :control_ports, :color_ram, :ieee, :mains_hz, :kernal, :cycles, :init_threshold
49
+
50
+ def ram = @bus.ram
51
+
52
+ # The VIC refreshes the DRAM in its own half of the cycle, so the
53
+ # refresh takes none of the CPU's.
54
+ def refresh? = false
55
+
56
+ def family = :cbm2
57
+
58
+ def peek(addr) = @bus.peek(addr)
59
+
60
+ def poke(addr, value) = @bus.poke(addr, value)
61
+
62
+ def install_debug_register(&) = @bus.install_debug_register(&)
63
+
64
+ def keyboard_buffer = CBM2::KEYBOARD_LAYOUT
65
+
66
+ def sound_source = @sid
67
+
68
+ # The VIC, whose display a front end shows.
69
+ def video = @vic
70
+
71
+ def region = @model.region
72
+
73
+ # +model+ names one of Model::ALL, and +mains_hz+ the mains, 50 or
74
+ # 60, the model's region's unless given.
75
+ def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false)
76
+ @model = Model.named(model)
77
+ @mains_hz = mains_hz || @model.region.mains_hz
78
+ raise ArgumentError, "the mains is 50 or 60 Hz, not #{@mains_hz}" unless [50, 60].include?(@mains_hz)
79
+
80
+ @kernal = Model::KERNALS.keys.first
81
+ clock_hz = @model.region.clock_hz
82
+ @bus = Bus.new(basic: Model::BASIC, kernal: Model::KERNALS.fetch(@kernal), ram_segments: Model::RAM_SEGMENTS,
83
+ screen_length: 0x400)
84
+ @vic = Chips::VIC.new(model: :mos6569, region: @model.region)
85
+ @color_ram = C64::ColorMemory.new(@vic, start: 0xd400)
86
+ map_video
87
+ @ieee = IEEEPorts.new(Drives::IEEEBus.new)
88
+ @tpi1 = Chips::TPI.new(peripheral: @ieee)
89
+ @joystick1 = Input::Joystick.new
90
+ @joystick2 = Input::Joystick.new
91
+ @control_ports = ControlPorts.new(@joystick1, @joystick2, @ieee.data)
92
+ @sid = Chips::SID.new(model: :mos6581, pots: @control_ports, at: 0xda00)
93
+ @cia = Chips::CIA.new(start: 0xdc00, peripheral: @control_ports, region: @model.region, tod_hz: @mains_hz)
94
+ @control_ports.connect(@cia)
95
+ @ieee.connect(@cia, @tpi1)
96
+ @lightpen_high = true
97
+ @acia = ACIA.new(self, clock_hz)
98
+ @keyboard = Input::Keyboard.new(matrix: KeyboardPorts::MATRIX)
99
+ keyboard_ports = KeyboardPorts.new(@keyboard, 0xc0)
100
+ @tpi2 = Chips::TPI.new(peripheral: keyboard_ports)
101
+ keyboard_ports.connect(@tpi2)
102
+ map_io
103
+ @cpu = CPU.new(@bus, debug:)
104
+ @cycles = 0
105
+ @mains_half_period = clock_hz / (2 * @mains_hz)
106
+ @mains_countdown = @mains_half_period
107
+ @mains_high = true
108
+ # The B series' refresh counter, which CBM2::KeyedState saves: 0
109
+ # here, where the VIC refreshes the DRAM.
110
+ @refresh = 0
111
+ @acia_irq = false
112
+ @acia_due = Chips::ACIA::NEVER
113
+ @init_handlers = []
114
+ @pending_keys = nil
115
+ @dos = nil
116
+ @ieee_trap = nil
117
+ @capture_output = nil
118
+ init_ieee_drives
119
+ @init_threshold = INIT_THRESHOLD
120
+ update_video_bus
121
+ end
122
+
123
+ # The model, its RAM and its mains as the pause menu names them, such
124
+ # as "510, 128K RAM, 50 HZ".
125
+ def model_label = "#{@model.name}, #{@model.ram_kb}K RAM, #{@mains_hz} HZ"
126
+
127
+ def timing
128
+ region = @model.region
129
+ Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
130
+ lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1, dots: 1)
131
+ end
132
+
133
+ # The VIC and the mains ahead of the chips, then TPI1's inputs from
134
+ # the CIA and the ACIA, and the CPU last, halted while BA is low on a
135
+ # read.
136
+ def cycle!
137
+ handle_init if @cycles == @init_threshold
138
+ feed_keyboard if @pending_keys
139
+
140
+ vic = @vic
141
+ vic.cycle!
142
+ tick_mains
143
+ @cia.cycle!
144
+ sample_lightpen
145
+ @sid.cycle!
146
+ @acia.run!(@cycles) if @cycles >= @acia_due
147
+ tpi1 = @tpi1
148
+ tpi1.interrupt_line(2, !@cia.interrupted?)
149
+ tpi1.interrupt_line(4, !@acia_irq)
150
+ cpu = @cpu
151
+ cpu.irq = tpi1.irq? || vic.interrupted?
152
+ cpu.pending_write? || !vic.ba_low? ? cpu.cycle! : cpu.stall!
153
+ clock_ieee_drives
154
+
155
+ @cycles += 1
156
+ end
157
+
158
+ # The ACIA's IRQ line, and the cycle it next has something to do at.
159
+ def acia_changed(irq, due)
160
+ @acia_irq = irq
161
+ @acia_due = due
162
+ end
163
+
164
+ # STATVID and VICDOTSEL from TPI1's CA and CB, PC6 and PC7 in mode 0,
165
+ # and VA14 and VA15 from TPI2's PC6 and PC7, to the VIC's bank.
166
+ def update_video_bus
167
+ lines = @tpi1.port_c_output
168
+ @vic.vic_bank.video_bus_lines(lines.anybits?(0x40), lines.anybits?(0x80), @tpi2.port_c_output >> 6)
169
+ end
170
+
171
+ # The RES line reaches the CPU and its segment registers, the SID, the
172
+ # CIA, the ACIA and both TPIs. The VIC has no reset pin.
173
+ def reset!
174
+ @sid.reset!
175
+ @cia.reset!
176
+ @acia.reset!
177
+ @tpi1.reset!
178
+ @tpi2.reset!
179
+ update_video_bus
180
+ @bus.reset!
181
+ @cpu.reset!
182
+ reset_ieee_drives
183
+ end
184
+
185
+ # Puts a program file's bytes where its first two say in BASIC's text
186
+ # segment, segment 0, and returns that address.
187
+ def load_prg(data)
188
+ load_addr = data[0] | (data[1] << 8)
189
+ bytes = data[2..] || []
190
+ bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
191
+ bytes.each_with_index { |byte, i| @bus.poke_bank(TEXT_SEGMENT, load_addr + i, byte) }
192
+ load_addr
193
+ end
194
+
195
+ # The IEEELayout of 901234-02.
196
+ def trap_layout = IEEELayout::LAYOUT
197
+
198
+ # Puts a cartridge's ROM chips in (Bus#attach_cartridge) and switches
199
+ # the machine off and on, for the KERNAL to find it as it starts.
200
+ def attach_cartridge(chips)
201
+ @bus.attach_cartridge(chips)
202
+ power_cycle!
203
+ end
204
+
205
+ # Switches the machine off and on: RAM starts cleared, the VIC starts
206
+ # over, and the RES line resets everything else.
207
+ def power_cycle!
208
+ @bus.power_on!
209
+ @color_ram.clear!
210
+ @vic.power_on!
211
+ reset!
212
+ end
213
+
214
+ # The cycle by which the KERNAL has tested segments 0 and 1 and BASIC
215
+ # printed READY, with a margin.
216
+ INIT_THRESHOLD = 8_600_000
217
+
218
+ private
219
+
220
+ # The character ROM at $C000 and the colour RAM at $D400 of segment
221
+ # 15, and what the VIC reads.
222
+ def map_video
223
+ character_rom = ROM.load("character.rom", 0xc000)
224
+ @bus.map_video(character_rom, 0xc0, 0x10)
225
+ @bus.map_video(ColorPages.new(@color_ram), 0xd4, 0x04, writable: true)
226
+ bank = @vic.vic_bank
227
+ bank.map_character_rom(character_rom, base: 0xc000)
228
+ bank.map_video_bus(@bus.segment_ram[0], @bus.screen, @color_ram, 0xd400)
229
+ end
230
+
231
+ def map_io
232
+ @bus.map_io(0xd8, VICPage.new(@vic))
233
+ @bus.map_io(0xda, SIDPage.new(@sid))
234
+ @bus.map_io(0xdc, @cia)
235
+ @bus.map_io(0xdd, @acia)
236
+ @bus.map_io(0xde, TPIPage.new(@tpi1, self))
237
+ @bus.map_io(0xdf, TPIPage.new(@tpi2, self))
238
+ end
239
+
240
+ # The VIC's light pen input, CIA PA6 as the CIA drives it and control
241
+ # port 1's fire line pulls it, whose falling edge latches the beam.
242
+ def sample_lightpen
243
+ high = @cia.port_a_lines.anybits?(0x40) && @control_ports.fire1_high?
244
+ return if high == @lightpen_high
245
+
246
+ @lightpen_high = high
247
+ @vic.lightpen_level(high)
248
+ end
249
+
250
+ # The mains, low for half its period and high for the other half, on
251
+ # TPI1's I0, whose falling edge latches the tick.
252
+ def tick_mains
253
+ @mains_countdown -= 1
254
+ return unless @mains_countdown.zero?
255
+
256
+ @mains_countdown = @mains_half_period
257
+ @mains_high = !@mains_high
258
+ @tpi1.interrupt_line(0, @mains_high)
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # Page $DA of segment 15: the SID's 32 registers, repeated over the
6
+ # page.
7
+ class SIDPage
8
+ def initialize(sid)
9
+ @sid = sid
10
+ end
11
+
12
+ def peek(addr) = @sid.peek(0xda00 | (addr & 0x1f))
13
+
14
+ def poke(addr, value)
15
+ @sid.poke(0xda00 | (addr & 0x1f), value)
16
+ end
17
+ end
18
+ end
19
+ end