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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/CLAUDE.md +20 -3
- data/CONTRIBUTING.md +8 -3
- data/README.md +6 -2
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/mmu.rb +15 -0
- data/lib/badline/c128/saved_state.rb +1 -0
- data/lib/badline/c128.rb +9 -7
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +6 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +13 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +2 -1
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/machine.rb +7 -5
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic20/attachments.rb +6 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +111 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +36 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2e262f4dbf07ef2d84f3a5eb673302102ecc9d4a5aa3ac87a908c6f0323c041
|
|
4
|
+
data.tar.gz: 026b7b26ce4cad94cf386a4b944c4ce34d34635e8dd8a568a352090320deabd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
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
|
|
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
|
data/lib/badline/c128/mmu.rb
CHANGED
|
@@ -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
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
data/lib/badline/cia/serial.rb
CHANGED
|
@@ -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?
|
data/lib/badline/cia/timer.rb
CHANGED
|
@@ -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
|