badline 0.11.0 → 0.11.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CLAUDE.md +20 -3
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +6 -2
  6. data/lib/badline/c128/drives.rb +95 -0
  7. data/lib/badline/c128/fast_serial.rb +62 -0
  8. data/lib/badline/c128/mmu.rb +15 -0
  9. data/lib/badline/c128/saved_state.rb +1 -0
  10. data/lib/badline/c128.rb +9 -7
  11. data/lib/badline/cia/fast_forward.rb +42 -0
  12. data/lib/badline/cia/serial.rb +12 -0
  13. data/lib/badline/cia/timer.rb +39 -0
  14. data/lib/badline/cia.rb +2 -0
  15. data/lib/badline/computer/attachments.rb +6 -0
  16. data/lib/badline/drive/core.rb +172 -0
  17. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  18. data/lib/badline/drive/idle_cpu.rb +28 -0
  19. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  20. data/lib/badline/drive/serial_lines.rb +73 -0
  21. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  22. data/lib/badline/drive/watch.rb +150 -0
  23. data/lib/badline/drive.rb +9 -0
  24. data/lib/badline/drive1541/bus.rb +5 -129
  25. data/lib/badline/drive1541/byte_ready.rb +48 -0
  26. data/lib/badline/drive1541/cpu.rb +1 -19
  27. data/lib/badline/drive1541/disk.rb +77 -20
  28. data/lib/badline/drive1541/disk_state.rb +7 -2
  29. data/lib/badline/drive1541/disk_via.rb +3 -0
  30. data/lib/badline/drive1541/mechanism.rb +48 -14
  31. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  32. data/lib/badline/drive1541/rotation.rb +5 -5
  33. data/lib/badline/drive1541/serial_port.rb +4 -60
  34. data/lib/badline/drive1541.rb +13 -172
  35. data/lib/badline/drive1571/bus.rb +111 -0
  36. data/lib/badline/drive1571/cpu.rb +11 -0
  37. data/lib/badline/drive1571/serial_port.rb +37 -0
  38. data/lib/badline/drive1571/serial_via.rb +25 -0
  39. data/lib/badline/drive1571/wd1770.rb +34 -0
  40. data/lib/badline/drive1571.rb +225 -0
  41. data/lib/badline/frontend/app.rb +1 -1
  42. data/lib/badline/frontend/drive_led.rb +1 -1
  43. data/lib/badline/frontend/menu_media.rb +3 -3
  44. data/lib/badline/frontend/menu_pages.rb +2 -1
  45. data/lib/badline/iec_bus.rb +54 -4
  46. data/lib/badline/machine.rb +7 -5
  47. data/lib/badline/media/true_drive.rb +26 -23
  48. data/lib/badline/options/help.rb +2 -1
  49. data/lib/badline/options/table.rb +1 -1
  50. data/lib/badline/roms/README +9 -0
  51. data/lib/badline/roms/dos1571.rom +0 -0
  52. data/lib/badline/storage/d64_image.rb +3 -0
  53. data/lib/badline/storage/d71_image.rb +9 -4
  54. data/lib/badline/storage/g64_image.rb +7 -2
  55. data/lib/badline/time_of_day.rb +9 -0
  56. data/lib/badline/version.rb +1 -1
  57. data/lib/badline/via/fast_forward.rb +3 -3
  58. data/lib/badline/vic20/attachments.rb +6 -0
  59. data/lib/badline/z80/arithmetic.rb +117 -0
  60. data/lib/badline/z80/bit_page.rb +55 -0
  61. data/lib/badline/z80/block_io.rb +58 -0
  62. data/lib/badline/z80/block_transfers.rb +59 -0
  63. data/lib/badline/z80/bus_cycles.rb +111 -0
  64. data/lib/badline/z80/core.rb +111 -0
  65. data/lib/badline/z80/exchanges.rb +50 -0
  66. data/lib/badline/z80/extended_page.rb +89 -0
  67. data/lib/badline/z80/flags.rb +37 -0
  68. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  69. data/lib/badline/z80/jumps.rb +82 -0
  70. data/lib/badline/z80/loads.rb +98 -0
  71. data/lib/badline/z80/main_page.rb +134 -0
  72. data/lib/badline/z80/registers.rb +120 -0
  73. data/lib/badline/z80/shifts.rb +64 -0
  74. data/lib/badline/z80.rb +11 -0
  75. metadata +36 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f8a451e4b936c57d5cf39cc7253481e5c8deff31898cb015641ed54246a68da
4
- data.tar.gz: 8d1e5c8aab0d9352e31a5f7b870896aaec254dbcd55c823fe24496d820b2befc
3
+ metadata.gz: c2e262f4dbf07ef2d84f3a5eb673302102ecc9d4a5aa3ac87a908c6f0323c041
4
+ data.tar.gz: 026b7b26ce4cad94cf386a4b944c4ce34d34635e8dd8a568a352090320deabd9
5
5
  SHA512:
6
- metadata.gz: d97d1b2876eafb8a51cd290c651904c798be975e0f3f7ea9ba4960a3dcaeebb6a76cc9cdad8bb5bf3ac5c732af3ec0eeddb779916b9e793292350d0749ed730f
7
- data.tar.gz: 2b7f0307a605b0270490ae3155c1671e1197473a0c22edf836bce953c08ff8884d1b09ca92fd7b3bc8c4038de63b66d5b92e3e82297ecfaddd5cde592ef74493
6
+ metadata.gz: d99d4042bdb920cfbaa9501a2fff2a6cc155dda76518b28a58e863d95bd18ef3d891f7dff5d935cc726ae380c9659bc09f5f11f7cefda657a4926bfc06974e76
7
+ data.tar.gz: 670579ee105286f5b5c04aaa63d5ae6a9fee9161f6735ccd9f7764152c43e421f6024a1ba7cfead0da92a7b87cab6f03b1b53a2d2e44693a62807b4f52ab81a3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.1](https://github.com/elektronaut/badline/compare/v0.11.0...v0.11.1) (2026-10-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * emulate the C128D's 1571 drive in 1541 and 1571 mode ([2c32d99](https://github.com/elektronaut/badline/commit/2c32d993a0449b4b537d5f82e9711b0e14604f95))
9
+ * fast serial between the C128 and its 1571 ([34fe02f](https://github.com/elektronaut/badline/commit/34fe02f2098664c94233da9e40828b40e41b6b1a))
10
+
3
11
  ## [0.11.0](https://github.com/elektronaut/badline/compare/v0.10.1...v0.11.0) (2026-10-09)
4
12
 
5
13
 
data/CLAUDE.md CHANGED
@@ -49,6 +49,7 @@ requires only the namespace file.
49
49
  | --- | --- | --- |
50
50
  | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
51
51
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
52
+ | SingleStepTests z80 | `rake test` (the first 100 cases per opcode, `Z80_SAMPLE=all` for all 1,604,000) | `Z80`: registers, flags, ports and the bus pins on every T-state |
52
53
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
53
54
  | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, the VIC-20 with `--vic20`, and the C128 in C64 mode with `--c128c64` |
54
55
  | VICE SID testprogs | `bin/sidtests` | SID |
@@ -103,7 +104,7 @@ worktree and owns a different set of files.
103
104
  - The external suites under `vendor/` are gitignored, so a new worktree has
104
105
  only `vendor/.gitkeep`. From the worktree root, symlink the main
105
106
  checkout's suites into it:
106
- `for d in 65x02 VICE-testprogs; do ln -sfn ../../../../vendor/$d vendor/$d; done`.
107
+ `for d in 65x02 z80-sample VICE-testprogs; do ln -sfn ../../../../vendor/$d vendor/$d; done`.
107
108
  The main checkout also has `vendor/OneLoad64-Games-Collection-v5`, which
108
109
  `rake vendor:checkout` doesn't fetch. Link it the same way only for
109
110
  `.sid` or media work. Don't re-run `rake vendor:checkout` there, and
@@ -129,12 +130,28 @@ worktree and owns a different set of files.
129
130
 
130
131
  ## Testing and baselines
131
132
 
132
- - `rake vendor:checkout` fetches SingleStepTests and VICE-testprogs into
133
- `vendor/`
133
+ - `rake vendor:checkout` fetches SingleStepTests 65x02, the Z80 sample
134
+ and VICE-testprogs into `vendor/`
134
135
  - `bundle exec rspec`: line coverage is about 97%. `spec/spec_helper.rb`
135
136
  fails a whole-suite run (every spec file, no filters) below 90%, while
136
137
  single-file and filtered runs skip the floor. `:slow` specs are excluded
137
138
  by default: run them with `bundle exec rspec --tag slow`
139
+ - CI splits the spec files into shards, plain and `:slow`, by the run times
140
+ in `test/spec_times.json` (`bin/spec_shard`), and holds the plain shards'
141
+ merged coverage to the same 90%. After adding or moving expensive specs,
142
+ re-record the times with `bin/spec_shard --record` (its header says how)
143
+ - Keep each plain example to a few hundred ms. A million cycles of a C64 or
144
+ a C128 cost 3 to 5 s of CPU, and about three times that under coverage,
145
+ so an example that boots a machine or runs more than about a million
146
+ cycles goes under `:slow`
147
+ - Don't boot per example. Restore a machine from a State taken once
148
+ (`spec/support/taken_once.rb`), and share one machine between examples
149
+ that only read it. Assert on the cheapest oracle that proves the point: a
150
+ register or a RAM byte over a booted screen, a bare chip over a whole
151
+ machine, and the fewest cycles that reach the state
152
+ - Before opening a pull request, run `bundle exec rspec --profile 10` (with
153
+ `--tag slow` for `:slow` ones) on the spec files you added or changed,
154
+ and hold them to that budget
138
155
  - `rake test`: SingleStepTests, plus the Minitest tests for the regression
139
156
  runners in `test/`. Run it for any CPU change
140
157
 
data/CONTRIBUTING.md CHANGED
@@ -68,12 +68,17 @@ boot the whole machine and are skipped by default. Run them with
68
68
  `bundle exec rspec --tag slow`.
69
69
 
70
70
  The CPU is verified separately against the
71
- [65x02 single step tests](https://github.com/SingleStepTests/65x02).
72
- `rake test` checks the fixtures out into `vendor/65x02` first, which
73
- takes a moment the first time:
71
+ [65x02 single step tests](https://github.com/SingleStepTests/65x02), and
72
+ the Z80 against the [Z80 ones](https://github.com/SingleStepTests/z80).
73
+ `rake test` checks the 65x02 fixtures out into `vendor/65x02` first,
74
+ which takes a moment the first time. For the Z80 it fetches only the
75
+ first 100 cases of each opcode's file into `vendor/z80-sample`.
76
+ `Z80_SAMPLE=all` checks the whole Z80 suite out into `vendor/z80`
77
+ (about 1.5 GB) and runs every case:
74
78
 
75
79
  ```sh
76
80
  bundle exec rake test
81
+ Z80_SAMPLE=all bundle exec rake test
77
82
  ```
78
83
 
79
84
  Check style before pushing:
data/README.md CHANGED
@@ -184,7 +184,8 @@ starts, and a `.sid` tune start C64 mode the same way, and so does a C64
184
184
  cartridge, through the KERNAL. `GO64` gets there from BASIC 7.0. A
185
185
  program that loads at `$1C01`, where BASIC 7.0 starts, runs, and other
186
186
  programs load at their own address. The disk in device 8 is served
187
- through the KERNAL traps in either mode. The C128's Z80 isn't emulated:
187
+ through the KERNAL traps in either mode, or with `--true-drive` by the
188
+ C128D's 1571. The C128's Z80 isn't emulated:
188
189
  the 8502 starts the machine. `--model` picks the board: `c128` (the default, the flat C128
189
190
  and the plastic C128D, PAL, with the 6581 SID and a VDC with 16K of
190
191
  RAM), `c128dcr` (the metal C128DCR, with the 8580, 6526As and a VDC with
@@ -348,8 +349,11 @@ program or `.sid` dropped on it opens the menu to ask first.
348
349
  - **C128**: C128 mode with BASIC 7.0 and C64 mode, on the 8502 with its
349
350
  port, FAST mode, the VIC-IIe with its extra keyboard lines, the 8722
350
351
  MMU, the 8563 or 8568 VDC with its 80 column display, and 128K of RAM
351
- (`badline c128`). The Z80 and the 1571 aren't emulated yet.
352
+ (`badline c128`). The Z80 isn't emulated yet.
352
353
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
354
+ - **1571**: the C128D's drive running DOS 3.0, in 1541 mode and in 1571
355
+ mode at 2 MHz with both sides of a `.d71` or `.g71`, and fast serial
356
+ (burst) to a C128 in C128 mode (`badline c128 --true-drive`).
353
357
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
354
358
  Fun Play / Power Play, Super Games, Epyx FastLoad, Westermann
355
359
  Learning, Rex Utility, C64 Game System / System 3, Dinamic, Zaxxon /
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The true drives on the C128's serial bus: a Drive1541, or the
6
+ # C128D's built-in 1571 (Drive1571), each in a field of its own, so
7
+ # neither makes the other's per-cycle call a union.
8
+ module Drives
9
+ attr_reader :drive1571
10
+
11
+ # Plugs in a Drive1571, which then runs alongside the C128 on its own
12
+ # clock and talks to it over the serial bus, as attach_drive1541 does
13
+ # for a 1541.
14
+ def attach_drive1571(drive)
15
+ @iec_bus.detach(@drive1571) if @drive1571
16
+ drive.host_clock_hz = region.clock_hz
17
+ @drive1571 = drive
18
+ drive.connect(iec_bus)
19
+ @serial_trap&.device = serial_trap_device
20
+ end
21
+
22
+ # Unplugs the Drive1571.
23
+ def detach_drive1571
24
+ return unless @drive1571
25
+
26
+ @iec_bus.detach(@drive1571)
27
+ @drive1571 = nil
28
+ @serial_trap&.device = serial_trap_device
29
+ end
30
+
31
+ # The true drive on the bus, the 1571 ahead of a 1541.
32
+ def true_drive = @drive1571 || @drive1541
33
+
34
+ # Plugs in the C128D's 1571 as device 8, and returns it.
35
+ def plug_true_drive = Drive1571.new.tap { |drive| attach_drive1571(drive) }
36
+
37
+ private
38
+
39
+ # The serial bus, with no disk in device 8's traps and no true drive.
40
+ def init_drives
41
+ @drive = nil
42
+ @serial_trap = nil
43
+ @save_trap = nil
44
+ @drive1541 = nil
45
+ @drive1571 = nil
46
+ plug_serial_bus
47
+ plug_fast_serial
48
+ end
49
+
50
+ # Device 8, unless a true drive is on the bus as device 8.
51
+ def serial_trap_device
52
+ device = KernalTrap::Routine::DEVICE
53
+ @drive1571&.device == device || @drive1541&.device == device ? nil : device
54
+ end
55
+
56
+ # A true 1541 (Computer::Attachments#save_serial_bus), then a 1571.
57
+ def save_serial_bus(out)
58
+ out.boolean(true).boolean(!@drive1541.nil?)
59
+ if @drive1541
60
+ out.int(@drive1541.device)
61
+ @drive1541.save_state(out)
62
+ end
63
+ out.boolean(!@drive1571.nil?)
64
+ return unless @drive1571
65
+
66
+ out.int(@drive1571.device)
67
+ @drive1571.save_state(out)
68
+ end
69
+
70
+ def load_serial_bus(input)
71
+ input.boolean?
72
+ load_drive1541(input)
73
+ load_drive1571(input)
74
+ end
75
+
76
+ def load_drive1541(input)
77
+ return detach_drive1541 unless input.boolean?
78
+
79
+ device = input.int
80
+ detach_drive1541 if @drive1541 && @drive1541.device != device
81
+ attach_drive1541(Drive1541.new(device:)) unless @drive1541
82
+ @drive1541.load_state(input)
83
+ end
84
+
85
+ def load_drive1571(input)
86
+ return detach_drive1571 unless input.boolean?
87
+
88
+ device = input.int
89
+ detach_drive1571 if @drive1571 && @drive1571.device != device
90
+ attach_drive1571(Drive1571.new(device:)) unless @drive1571
91
+ @drive1571.load_state(input)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # Fast serial: CIA 1's CNT and SP on the serial bus's SRQ and DATA,
6
+ # through buffers MCR's FSDIR turns round. Outwards, the machine pushes
7
+ # the lines the pins pull into the bus as they move. Inwards, CIA 1
8
+ # hears the lines whenever a drive moves its own fast serial pins
9
+ # (IECBus#on_fast_change).
10
+ module FastSerial
11
+ private
12
+
13
+ def plug_fast_serial
14
+ @fast_serial_out = false
15
+ @bus.mmu.on_fast_serial_change { turn_fast_serial }
16
+ @iec_bus.on_fast_change { hear_fast_serial unless @fast_serial_out }
17
+ end
18
+
19
+ # The machine's side of the serial bus each cycle: CIA 1's pins
20
+ # pushed while FSDIR has them out, then the drives.
21
+ def clock_serial_bus
22
+ drive_fast_serial if @fast_serial_out
23
+ @drive1541&.host_cycle!
24
+ @drive1571&.host_cycle!
25
+ end
26
+
27
+ def turn_fast_serial
28
+ push_fast_serial
29
+ hear_fast_serial unless @fast_serial_out
30
+ end
31
+
32
+ # Pushes the lines CIA 1's pins pull while FSDIR has them out. A
33
+ # restored machine pushes them again, its CIA 1 hearing the lines as
34
+ # it last did.
35
+ def push_fast_serial
36
+ @fast_serial_out = @bus.mmu.fast_serial_out?
37
+ @iec_bus.host_fast_lines = @fast_serial_out ? fast_pins : 0
38
+ end
39
+
40
+ def drive_fast_serial
41
+ @iec_bus.host_fast_lines = fast_pins
42
+ end
43
+
44
+ # The lines CIA 1's CNT and SP pull while its serial port drives
45
+ # them.
46
+ def fast_pins
47
+ serial = @cia1.serial
48
+ return 0 unless serial.output?
49
+
50
+ low = serial.cnt ? 0 : IECBus::SRQ
51
+ serial.sp_out ? low : low | IECBus::DATA
52
+ end
53
+
54
+ def hear_fast_serial
55
+ low = @iec_bus.low_lines
56
+ serial = @cia1.serial
57
+ serial.cnt_in = low.nobits?(IECBus::SRQ)
58
+ serial.sp_in = low.nobits?(IECBus::DATA)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -36,6 +36,7 @@ module Badline
36
36
  VR = 11
37
37
 
38
38
  MCR_8502 = 0x01
39
+ MCR_FSDIR = 0x08
39
40
  MCR_C64_MODE = 0x40
40
41
  VERSION = 0x20
41
42
 
@@ -68,6 +69,7 @@ module Badline
68
69
  @game = 1
69
70
  @exrom = 1
70
71
  @on_change = nil
72
+ @on_fast_serial_change = nil
71
73
  reset!
72
74
  end
73
75
 
@@ -76,7 +78,13 @@ module Badline
76
78
  @on_change = block
77
79
  end
78
80
 
81
+ # Calls the block whenever FSDIR turns the fast serial buffers round.
82
+ def on_fast_serial_change(&block)
83
+ @on_fast_serial_change = block
84
+ end
85
+
79
86
  def reset!
87
+ fast_serial_out = fast_serial_out?
80
88
  @registers.fill(0)
81
89
  @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : MCR_8502
82
90
  @registers[P1L] = 0x01
@@ -84,10 +92,15 @@ module Badline
84
92
  @p0h_latch = 0
85
93
  @p1h_latch = 0
86
94
  @on_change&.call
95
+ @on_fast_serial_change&.call if fast_serial_out
87
96
  end
88
97
 
89
98
  def c64_mode? = @registers[MCR].anybits?(MCR_C64_MODE)
90
99
 
100
+ # Whether MCR bit 3, FSDIR, turns the fast serial buffers outwards, so
101
+ # CIA 1's CNT and SP drive SRQ and DATA.
102
+ def fast_serial_out? = @registers[MCR].anybits?(MCR_FSDIR)
103
+
91
104
  # :c64 or :c128, the mode MCR bit 6 selects.
92
105
  def mode = c64_mode? ? :c64 : :c128
93
106
 
@@ -174,8 +187,10 @@ module Badline
174
187
  end
175
188
 
176
189
  def write(offset, value)
190
+ fast_serial_out = fast_serial_out?
177
191
  @registers[offset] = value
178
192
  @on_change&.call
193
+ @on_fast_serial_change&.call if fast_serial_out? != fast_serial_out
179
194
  end
180
195
  end
181
196
  end
@@ -103,6 +103,7 @@ module Badline
103
103
  @cpu.load_state(input)
104
104
  load_trap_drive(input)
105
105
  load_serial_bus(input)
106
+ push_fast_serial
106
107
  end
107
108
 
108
109
  private
data/lib/badline/c128.rb CHANGED
@@ -12,6 +12,9 @@ require "badline/c128/cpu"
12
12
  require "badline/c128/saved_state"
13
13
  require "badline/c128/keys"
14
14
  require "badline/c128/modes"
15
+ require "badline/c128/drives"
16
+ require "badline/c128/fast_serial"
17
+ require "badline/drive1571"
15
18
 
16
19
  module Badline
17
20
  # The Commodore 128: the 8502 on the VIC-IIe's clock, two CIAs, the SID,
@@ -40,6 +43,8 @@ module Badline
40
43
  include Computer::KernalTraps
41
44
  include Keys
42
45
  include Modes
46
+ include Drives
47
+ include FastSerial
43
48
 
44
49
  # The C64's 8x8 matrix and the three rows K0-K2 select, in port B
45
50
  # column order.
@@ -125,12 +130,8 @@ module Badline
125
130
  @restore_pulse = false
126
131
  @init_handlers = []
127
132
  @pending_keys = nil
128
- @drive = nil
129
- @serial_trap = nil
130
- @save_trap = nil
131
- @drive1541 = nil
132
133
  @capture_output = nil
133
- plug_serial_bus
134
+ init_drives
134
135
  enter_c64_mode if @c64_built
135
136
  @bus.on_mode_change { mode_changed }
136
137
  end
@@ -158,7 +159,7 @@ module Badline
158
159
 
159
160
  drive_nmi
160
161
  clock_bits.odd? ? clock_fast : clock_cpu
161
- @drive1541&.host_cycle!
162
+ clock_serial_bus
162
163
 
163
164
  @cycles += 1
164
165
  end
@@ -230,7 +231,7 @@ module Badline
230
231
  end
231
232
 
232
233
  # The RES line reaches the CPU and its port, the MMU, both CIAs, the
233
- # SID, the cartridge port and, through the serial bus, the drive.
234
+ # SID, the cartridge port and, through the serial bus, the drives.
234
235
  def reset!
235
236
  @bus.reset!
236
237
  @cia1.reset!
@@ -239,6 +240,7 @@ module Badline
239
240
  @bus.cartridge&.reset
240
241
  @drive&.reset!
241
242
  @drive1541&.reset!
243
+ @drive1571&.reset!
242
244
  @nmi_asserted = false
243
245
  @cpu.reset!
244
246
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CIA
5
+ # Running a CIA a stretch of cycles at once, for Drive::Idle.
6
+ module FastForward
7
+ # Whether fast_forward can run the CIA: timer B stopped with nothing
8
+ # in its pipeline, no interrupt or acknowledge on its way, CNT where it
9
+ # was, the TOD clock stopped, and timer A either stopped, or free
10
+ # running (Timer#free_running?) with its interrupt masked and the
11
+ # serial port an idle input. Drive::Idle sleeps the 1571's CIA through
12
+ # such cycles, which its DOS leaves timer A running in.
13
+ def quiet?
14
+ return false unless @tb.idle? && @icr.quiet && @tod.quiet? && cnt_steady?
15
+
16
+ @ta.idle? ? @serial.idle : timer_a_free_running?
17
+ end
18
+
19
+ # Runs +cycles+ cycles at once while quiet?, as cycle! would: timer A's
20
+ # underflows flag in the ICR, and reach the serial port's underflow
21
+ # line.
22
+ def fast_forward(cycles)
23
+ @tod.fast_forward(cycles)
24
+ return if @ta.idle?
25
+
26
+ @icr.flag(:timer_a) if @ta.fast_forward(cycles).positive?
27
+ @serial.follow_underflow(@ta.underflowed)
28
+ end
29
+
30
+ private
31
+
32
+ # Timer A free running with its interrupt masked, and the serial port
33
+ # an idle input it only clocks.
34
+ def timer_a_free_running?
35
+ @ta.free_running? && !@control_a.serial_mode? && @icr.mask.value.nobits?(0x01) && @serial.drained?
36
+ end
37
+
38
+ # Whether CNT holds the level the last cycle sampled.
39
+ def cnt_steady? = @serial.cnt == @cnt_high && !@cnt_rise
40
+ end
41
+ end
42
+ end
@@ -79,6 +79,18 @@ module Badline
79
79
  @control.serial_mode?
80
80
  end
81
81
 
82
+ # Whether nothing is counting down or in the delay lines, so only
83
+ # timer A's underflows reach the register.
84
+ def drained? = idle?
85
+
86
+ # Takes timer A's underflow line as cycle! would have left it, with
87
+ # +underflowed+ its level on the last cycle, for a CIA run without
88
+ # the register in use (CIA#fast_forward).
89
+ def follow_underflow(underflowed)
90
+ @underflow_high = underflowed
91
+ @idle = !underflowed
92
+ end
93
+
82
94
  def cnt_in=(level)
83
95
  @cnt_in = level
84
96
  @cnt = level unless output?
@@ -24,6 +24,32 @@ module Badline
24
24
  control.out_mode? ? @toggle : @underflowed
25
25
  end
26
26
 
27
+ # Whether the timer is stopped with nothing in its pipeline, so a
28
+ # cycle changes nothing.
29
+ def idle? = @empty && !started?
30
+
31
+ # Whether the timer counts ø2 in continuous mode with nothing pending
32
+ # but the reload after an underflow, so fast_forward can run it. A
33
+ # latch below 2 is left out.
34
+ def free_running?
35
+ started? && !@control.run_mode? && @control.value.nobits?(0x20) && @latch >= 2 && @pipe == 0b11 &&
36
+ (@load_delay | @oneshot_linger).zero? && (@settled || @reload)
37
+ end
38
+
39
+ # Runs a free-running timer +cycles+ cycles at once, as cycle! would:
40
+ # an underflow every latch + 1 cycles, each one followed by the
41
+ # reload. Returns the underflows.
42
+ def fast_forward(cycles)
43
+ return 0 if cycles.zero?
44
+ return after_underflow(cycles, 0) unless @settled
45
+
46
+ if cycles < @counter
47
+ @counter -= cycles
48
+ return 0
49
+ end
50
+ after_underflow(cycles - @counter, 1)
51
+ end
52
+
27
53
  def cycle!(feed)
28
54
  feed &&= @control.value & 0x01 != 0
29
55
  if @settled
@@ -98,6 +124,19 @@ module Badline
98
124
  @control.value & 0x01 != 0
99
125
  end
100
126
 
127
+ # Where fast_forward lands +cycles+ after an underflow, with
128
+ # +underflows+ underflows counted so far.
129
+ def after_underflow(cycles, underflows)
130
+ periods, rest = cycles.divmod(@latch + 1)
131
+ underflows += periods
132
+ @toggle = !@toggle if underflows.odd?
133
+ @underflowed = rest.zero?
134
+ @reload = rest.zero?
135
+ @counter = @latch - (rest.zero? ? 0 : rest - 1)
136
+ settle
137
+ underflows
138
+ end
139
+
101
140
  def enter
102
141
  @pipe = 0b10
103
142
  @empty = false
data/lib/badline/cia.rb CHANGED
@@ -4,12 +4,14 @@ require "badline/cia/interrupt_register"
4
4
  require "badline/cia/serial"
5
5
  require "badline/cia/timer"
6
6
  require "badline/cia/state"
7
+ require "badline/cia/fast_forward"
7
8
 
8
9
  module Badline
9
10
  # CIA (Complex Interface Adapter) chip
10
11
  class CIA
11
12
  include Addressable
12
13
  include State
14
+ include FastForward
13
15
 
14
16
  # The original NMOS 6526 of the breadbin C64, and the HMOS 6526A of the
15
17
  # C64C, which differs from it only in its interrupt control register.
@@ -57,6 +57,12 @@ module Badline
57
57
  @serial_trap&.device = serial_trap_device
58
58
  end
59
59
 
60
+ # The true drive on the serial bus: the Drive1541, or nil.
61
+ def true_drive = @drive1541
62
+
63
+ # Plugs a Drive1541 in as device 8, and returns it.
64
+ def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
65
+
60
66
  # Unplugs the Drive1541, leaving the serial bus with nothing on it.
61
67
  def detach_drive1541
62
68
  return unless @drive1541