badline 0.11.0 → 0.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/CLAUDE.md +24 -6
- data/CONTRIBUTING.md +15 -9
- data/README.md +16 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/c128/bus.rb +4 -17
- data/lib/badline/c128/drives.rb +108 -0
- data/lib/badline/c128/fast_serial.rb +63 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +24 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +23 -0
- data/lib/badline/c128/z80.rb +49 -0
- data/lib/badline/c128/z80_pages.rb +118 -0
- data/lib/badline/c128/z80_turns.rb +106 -0
- data/lib/badline/c128.rb +25 -26
- data/lib/badline/cartridge/action_replay.rb +2 -2
- data/lib/badline/cartridge/comal80.rb +1 -7
- data/lib/badline/cartridge/dinamic.rb +0 -8
- data/lib/badline/cartridge/final_cartridge3.rb +6 -6
- data/lib/badline/cartridge/fun_play.rb +0 -8
- data/lib/badline/cartridge/game_system.rb +0 -8
- data/lib/badline/cartridge/kcs_power.rb +10 -7
- data/lib/badline/cartridge/mach5.rb +2 -4
- data/lib/badline/cartridge/magic_desk.rb +0 -8
- data/lib/badline/cartridge/ocean.rb +0 -8
- data/lib/badline/cartridge/pagefox.rb +2 -2
- data/lib/badline/cartridge/retro_replay.rb +7 -6
- data/lib/badline/cartridge/rex_utility.rb +2 -6
- data/lib/badline/cartridge/rgcd.rb +2 -2
- data/lib/badline/cartridge/simons_basic.rb +0 -5
- data/lib/badline/cartridge/super_games.rb +4 -2
- data/lib/badline/cartridge/westermann.rb +2 -6
- data/lib/badline/cartridge.rb +37 -9
- data/lib/badline/cia/fast_forward.rb +60 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +10 -5
- data/lib/badline/computer/attachments.rb +4 -50
- data/lib/badline/computer/true_drives.rb +80 -0
- data/lib/badline/computer.rb +4 -0
- data/lib/badline/core.rb +2 -0
- data/lib/badline/drive/core.rb +147 -0
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +11 -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 +14 -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 +120 -0
- data/lib/badline/drive1571.rb +175 -0
- data/lib/badline/drive1581/bus.rb +137 -0
- data/lib/badline/drive1581/cpu.rb +11 -0
- data/lib/badline/drive1581/disk.rb +135 -0
- data/lib/badline/drive1581/disk_state.rb +87 -0
- data/lib/badline/drive1581/mechanism.rb +117 -0
- data/lib/badline/drive1581/serial_port.rb +52 -0
- data/lib/badline/drive1581/slot.rb +78 -0
- data/lib/badline/drive1581/track.rb +100 -0
- data/lib/badline/drive1581/track_bytes.rb +75 -0
- data/lib/badline/drive1581/wd1772/registers.rb +89 -0
- data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
- data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
- data/lib/badline/drive1581/wd1772/state.rb +66 -0
- data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
- data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
- data/lib/badline/drive1581/wd1772/turn.rb +73 -0
- data/lib/badline/drive1581/wd1772.rb +216 -0
- data/lib/badline/drive1581.rb +233 -0
- data/lib/badline/frontend/app.rb +2 -4
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +18 -10
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +33 -8
- data/lib/badline/frontend/media_slots.rb +42 -0
- data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
- data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
- data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
- data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
- data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
- data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
- data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
- data/lib/badline/frontend/menu/theme.rb +18 -0
- data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
- data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
- data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
- data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
- data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
- data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
- data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
- data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
- data/lib/badline/frontend/player/theme.rb +18 -0
- data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
- data/lib/badline/frontend/sound.rb +19 -38
- data/lib/badline/frontend/timeline.rb +3 -14
- data/lib/badline/frontend.rb +24 -20
- data/lib/badline/iec_bus.rb +68 -6
- data/lib/badline/machine.rb +9 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/true_drive.rb +48 -25
- data/lib/badline/media.rb +9 -3
- data/lib/badline/options/event.rb +2 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +3 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/snapshot/state.rb +5 -4
- 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/storage/p00.rb +8 -0
- 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 +23 -3
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -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 +113 -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/saved_state.rb +69 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +89 -24
- /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
- /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
- /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
- /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class VIA
|
|
5
5
|
# Running a VIA a stretch of cycles at once while nothing but its
|
|
6
|
-
# counters would change, for
|
|
6
|
+
# counters would change, for Drive::Idle.
|
|
7
7
|
module FastForward
|
|
8
8
|
# quiet_cycles for a VIA that will never set a flag of its own.
|
|
9
9
|
QUIET = 1 << 40
|
|
10
10
|
|
|
11
11
|
# How many cycles the VIA can run with nothing but its counters
|
|
12
12
|
# changing: no flag set, no pulse on a control line and no bit
|
|
13
|
-
# shifted. See
|
|
13
|
+
# shifted. See Drive::Idle.
|
|
14
14
|
def quiet_cycles
|
|
15
15
|
return 0 if @sr_uses_t2 || @shift_register.clocking? || @ca.pulsing? || @cb.pulsing?
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ module Badline
|
|
|
26
26
|
@t2.fast_forward(cycles)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# The counters an orbit (see
|
|
29
|
+
# The counters an orbit (see Drive::Idle) has to bring back: those
|
|
30
30
|
# of the armed timers, and of the timers +touched+ names (bit 0 for
|
|
31
31
|
# timer 1, bit 1 for timer 2). The others only count.
|
|
32
32
|
def counter_state(touched)
|
|
@@ -4,8 +4,10 @@ module Badline
|
|
|
4
4
|
class Vic20
|
|
5
5
|
# What plugs into the VIC-20 besides its chips: cartridge ROM in the
|
|
6
6
|
# expansion blocks, the disk device 8 serves through the KERNAL traps,
|
|
7
|
-
# and a true 1541 on the serial bus.
|
|
7
|
+
# and a true 1541 or 1581 on the serial bus.
|
|
8
8
|
module Attachments
|
|
9
|
+
include Drive1581::Slot
|
|
10
|
+
|
|
9
11
|
# The cycle by which a 1541 switched on with the machine has run its
|
|
10
12
|
# DOS's reset, the RAM test and the ROM checksum, and waits on the
|
|
11
13
|
# bus, with a margin: about 1.1 million of the VIC-20's cycles.
|
|
@@ -48,7 +50,7 @@ module Badline
|
|
|
48
50
|
|
|
49
51
|
cpu.remove_trap(KernalTrap::VIC20_LAYOUT.load)
|
|
50
52
|
cpu.remove_trap(KernalTrap::VIC20_LAYOUT.save)
|
|
51
|
-
@serial_trap.
|
|
53
|
+
@serial_trap.uninstall
|
|
52
54
|
@serial_trap = nil
|
|
53
55
|
@save_trap = nil
|
|
54
56
|
@drive = nil
|
|
@@ -75,6 +77,17 @@ module Badline
|
|
|
75
77
|
@serial_trap&.device = serial_trap_device
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
# The true drive on the serial bus: the Drive1581 ahead of the
|
|
81
|
+
# Drive1541, or nil.
|
|
82
|
+
def true_drive = @drive1581 || @drive1541
|
|
83
|
+
|
|
84
|
+
# Plugs a Drive1541 in as device 8, in place of a 1581 there, and
|
|
85
|
+
# returns it.
|
|
86
|
+
def plug_true_drive
|
|
87
|
+
detach_drive1581 if drive1581_on_device8?
|
|
88
|
+
Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
89
|
+
end
|
|
90
|
+
|
|
78
91
|
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
79
92
|
def detach_drive1541
|
|
80
93
|
return unless @drive1541
|
|
@@ -96,7 +109,14 @@ module Badline
|
|
|
96
109
|
# The device number the serial traps answer: device 8, unless a true
|
|
97
110
|
# drive is on the bus as device 8.
|
|
98
111
|
def serial_trap_device
|
|
99
|
-
|
|
112
|
+
device = KernalTrap::Routine::DEVICE
|
|
113
|
+
@drive1541&.device == device || drive1581_on_device8? ? nil : device
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A 1581 plugged in, as a 1541 does, holds the machine's on_init
|
|
117
|
+
# handlers back until its DOS has booted.
|
|
118
|
+
def drive1581_attached
|
|
119
|
+
@init_threshold = [@init_threshold, Drive1581::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
100
120
|
end
|
|
101
121
|
end
|
|
102
122
|
end
|
|
@@ -78,6 +78,7 @@ module Badline
|
|
|
78
78
|
@datasette.save_state(out)
|
|
79
79
|
save_trap_drive(out)
|
|
80
80
|
save_drive1541(out)
|
|
81
|
+
save_drive1581(out)
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def load_state(input)
|
|
@@ -99,6 +100,7 @@ module Badline
|
|
|
99
100
|
@datasette.load_state(input)
|
|
100
101
|
load_trap_drive(input)
|
|
101
102
|
load_drive1541(input)
|
|
103
|
+
load_drive1581(input)
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
private
|
data/lib/badline/vic20.rb
CHANGED
|
@@ -75,6 +75,7 @@ module Badline
|
|
|
75
75
|
@save_trap = nil
|
|
76
76
|
@capture_output = nil
|
|
77
77
|
@drive1541 = nil
|
|
78
|
+
@drive1581 = nil
|
|
78
79
|
@init_threshold = boot_cycles
|
|
79
80
|
wire_ports
|
|
80
81
|
end
|
|
@@ -94,6 +95,7 @@ module Badline
|
|
|
94
95
|
drive_nmi
|
|
95
96
|
@cpu.cycle!
|
|
96
97
|
@drive1541&.host_cycle!
|
|
98
|
+
@drive1581&.host_cycle!
|
|
97
99
|
|
|
98
100
|
@cycles += 1
|
|
99
101
|
end
|
|
@@ -176,6 +178,7 @@ module Badline
|
|
|
176
178
|
via_written
|
|
177
179
|
@drive&.reset!
|
|
178
180
|
@drive1541&.reset!
|
|
181
|
+
@drive1581&.reset!
|
|
179
182
|
@nmi_asserted = false
|
|
180
183
|
@cpu.reset!
|
|
181
184
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# The ALU's 8- and 16-bit arithmetic. Bits 5 and 3 of F come from the
|
|
6
|
+
# result, except for CP, which takes them from the operand, and the
|
|
7
|
+
# 16-bit operations, which take them from the high byte.
|
|
8
|
+
module Arithmetic
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# The eight operations an opcode's bits 5-3 pick: ADD ADC SUB SBC AND
|
|
12
|
+
# XOR OR CP.
|
|
13
|
+
def alu(operation, value)
|
|
14
|
+
case operation
|
|
15
|
+
when 0 then add_a(value, 0)
|
|
16
|
+
when 1 then add_a(value, @f & CF)
|
|
17
|
+
when 2 then @a = subtract(value, 0)
|
|
18
|
+
when 3 then @a = subtract(value, @f & CF)
|
|
19
|
+
when 4 then logic(@a & value, HF)
|
|
20
|
+
when 5 then logic(@a ^ value, 0)
|
|
21
|
+
when 6 then logic(@a | value, 0)
|
|
22
|
+
else compare(value)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_a(value, carry)
|
|
27
|
+
result = @a + value + carry
|
|
28
|
+
assign_flags(SZ53[result & 0xff] | (result >> 8) | ((@a ^ value ^ result) & HF) |
|
|
29
|
+
(((@a ^ value ^ 0x80) & (@a ^ result) & 0x80) >> 5))
|
|
30
|
+
@a = result & 0xff
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def subtract(value, carry)
|
|
34
|
+
result = @a - value - carry
|
|
35
|
+
assign_flags(SZ53[result & 0xff] | NF | ((result >> 8) & CF) | ((@a ^ value ^ result) & HF) |
|
|
36
|
+
(((@a ^ value) & (@a ^ result) & 0x80) >> 5))
|
|
37
|
+
result & 0xff
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def compare(value)
|
|
41
|
+
subtract(value, 0)
|
|
42
|
+
assign_flags((@f & ~XYF) | (value & XYF))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def logic(result, half)
|
|
46
|
+
@a = result
|
|
47
|
+
assign_flags(SZ53P[result] | half)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def increment(value)
|
|
51
|
+
result = (value + 1) & 0xff
|
|
52
|
+
assign_flags((@f & CF) | SZ53[result] | (result == 0x80 ? PF : 0) | (result.nobits?(0x0f) ? HF : 0))
|
|
53
|
+
result
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def decrement(value)
|
|
57
|
+
result = (value - 1) & 0xff
|
|
58
|
+
assign_flags((@f & CF) | NF | SZ53[result] | (result == 0x7f ? PF : 0) | (value.nobits?(0x0f) ? HF : 0))
|
|
59
|
+
result
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def add16(left, right)
|
|
63
|
+
result = left + right
|
|
64
|
+
@wz = (left + 1) & 0xffff
|
|
65
|
+
assign_flags((@f & (SF | ZF | PF)) | ((result >> 8) & XYF) | (((left ^ right ^ result) >> 8) & HF) |
|
|
66
|
+
(result >> 16))
|
|
67
|
+
result & 0xffff
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def adc16(right)
|
|
71
|
+
left = hl
|
|
72
|
+
result = left + right + (@f & CF)
|
|
73
|
+
@wz = (left + 1) & 0xffff
|
|
74
|
+
self.hl = result & 0xffff
|
|
75
|
+
assign_flags(word_flags(result) | (((left ^ right ^ result) >> 8) & HF) |
|
|
76
|
+
(((left ^ right ^ 0x8000) & (left ^ result) & 0x8000) >> 13))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def sbc16(right)
|
|
80
|
+
left = hl
|
|
81
|
+
result = left - right - (@f & CF)
|
|
82
|
+
@wz = (left + 1) & 0xffff
|
|
83
|
+
self.hl = result & 0xffff
|
|
84
|
+
assign_flags(word_flags(result) | NF | (((left ^ right ^ result) >> 8) & HF) |
|
|
85
|
+
(((left ^ right) & (left ^ result) & 0x8000) >> 13))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def word_flags(result)
|
|
89
|
+
((result >> 8) & (SF | XYF)) | (result.nobits?(0xffff) ? ZF : 0) | ((result >> 16) & CF)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def daa
|
|
93
|
+
low = @a & 0x0f
|
|
94
|
+
carry = @f.anybits?(CF) || @a > 0x99 ? CF : 0
|
|
95
|
+
correction = (carry.zero? ? 0 : 0x60) | (@f.anybits?(HF) || low > 9 ? 0x06 : 0)
|
|
96
|
+
subtracting = @f.anybits?(NF)
|
|
97
|
+
half = daa_half(low, subtracting)
|
|
98
|
+
@a = (subtracting ? @a - correction : @a + correction) & 0xff
|
|
99
|
+
assign_flags(SZ53P[@a] | (@f & NF) | carry | half)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def daa_half(low, subtracting)
|
|
103
|
+
if subtracting
|
|
104
|
+
@f.anybits?(HF) && low < 6 ? HF : 0
|
|
105
|
+
else
|
|
106
|
+
low > 9 ? HF : 0
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def neg
|
|
111
|
+
value = @a
|
|
112
|
+
@a = 0
|
|
113
|
+
@a = subtract(value, 0)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# The CB page: rotates and shifts (quarter 0), BIT (1), RES (2) and SET
|
|
6
|
+
# (3) of the bit the row names, on the column's register or (HL). After
|
|
7
|
+
# DD or FD the operand is always (IX+d): the displacement comes before
|
|
8
|
+
# the opcode, and an opcode that names a register other than (HL) also
|
|
9
|
+
# copies the result into it.
|
|
10
|
+
module BitPage
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def execute_bits(opcode)
|
|
14
|
+
operation = opcode >> 6
|
|
15
|
+
number = (opcode >> 3) & 7
|
|
16
|
+
index = opcode & 7
|
|
17
|
+
return bits_in_memory(operation, number, hl) if index == 6
|
|
18
|
+
|
|
19
|
+
value = register(index)
|
|
20
|
+
if operation == 1
|
|
21
|
+
bit(number, value, value)
|
|
22
|
+
else
|
|
23
|
+
set_register(index, bit_result(operation, number, value))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def execute_index_bits
|
|
28
|
+
displaced
|
|
29
|
+
opcode = fetch_byte
|
|
30
|
+
@cycles += 2
|
|
31
|
+
result = bits_in_memory(opcode >> 6, (opcode >> 3) & 7, @wz)
|
|
32
|
+
index = opcode & 7
|
|
33
|
+
set_plain_register(index, result) unless index == 6 || (opcode >> 6) == 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def bits_in_memory(operation, number, address)
|
|
37
|
+
value = read_byte(address)
|
|
38
|
+
@cycles += 1
|
|
39
|
+
return bit(number, value, @wz >> 8) if operation == 1
|
|
40
|
+
|
|
41
|
+
result = bit_result(operation, number, value)
|
|
42
|
+
write_byte(address, result)
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def bit_result(operation, number, value)
|
|
47
|
+
case operation
|
|
48
|
+
when 0 then shift(number, value)
|
|
49
|
+
when 2 then value & ~(1 << number) & 0xff
|
|
50
|
+
else value | (1 << number)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# INI, IND, OUTI, OUTD and their repeating forms. B counts the bytes
|
|
6
|
+
# and sets S, Z, 5 and 3. The byte's bit 7 sets N, and adding it to C
|
|
7
|
+
# plus or minus one for INI and IND, or to L for OUTI and OUTD, sets H
|
|
8
|
+
# and C on a carry out and P/V from the parity of its low 3 bits
|
|
9
|
+
# exclusive-ored with B.
|
|
10
|
+
module BlockIO
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def block_input(step, repeat)
|
|
14
|
+
@cycles += 1
|
|
15
|
+
port = bc
|
|
16
|
+
value = input(port)
|
|
17
|
+
@wz = (port + step) & 0xffff
|
|
18
|
+
@b = (@b - 1) & 0xff
|
|
19
|
+
write_byte(hl, value)
|
|
20
|
+
self.hl = (hl + step) & 0xffff
|
|
21
|
+
block_io_flags(value, value + ((@c + step) & 0xff), repeat)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def block_output(step, repeat)
|
|
25
|
+
@cycles += 1
|
|
26
|
+
value = read_byte(hl)
|
|
27
|
+
@b = (@b - 1) & 0xff
|
|
28
|
+
output(bc, value)
|
|
29
|
+
self.hl = (hl + step) & 0xffff
|
|
30
|
+
@wz = (bc + step) & 0xffff
|
|
31
|
+
block_io_flags(value, value + @l, repeat)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def block_io_flags(value, sum, repeat)
|
|
35
|
+
carry = sum > 0xff ? HF | CF : 0
|
|
36
|
+
assign_flags(SZ53[@b] | ((value >> 6) & NF) | carry | PARITY[(sum & 7) ^ @b])
|
|
37
|
+
return unless repeat && !@b.zero?
|
|
38
|
+
|
|
39
|
+
repeat_block
|
|
40
|
+
repeat_io_flags(value, carry)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def repeat_io_flags(value, carry)
|
|
44
|
+
flags = @f & ~HF
|
|
45
|
+
if carry.zero?
|
|
46
|
+
flags ^= PARITY[@b & 7] ^ PF
|
|
47
|
+
elsif value.anybits?(0x80)
|
|
48
|
+
flags ^= PARITY[(@b - 1) & 7] ^ PF
|
|
49
|
+
flags |= HF if @b.nobits?(0x0f)
|
|
50
|
+
else
|
|
51
|
+
flags ^= PARITY[(@b + 1) & 7] ^ PF
|
|
52
|
+
flags |= HF if @b.allbits?(0x0f)
|
|
53
|
+
end
|
|
54
|
+
assign_flags(flags)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# LDI, LDD, CPI, CPD and their repeating forms. A repeating form that
|
|
6
|
+
# goes round again winds the program counter back to its ED and spends
|
|
7
|
+
# 5 T-states more, and bits 5 and 3 then come from the program
|
|
8
|
+
# counter's high byte.
|
|
9
|
+
module BlockTransfers
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# ED A0-BB: the column picks LD, CP, IN or OUT, bit 3 counts down, and
|
|
13
|
+
# bit 4 repeats.
|
|
14
|
+
def block_operation(opcode)
|
|
15
|
+
step = opcode.nobits?(0x08) ? 1 : -1
|
|
16
|
+
repeat = opcode.anybits?(0x10)
|
|
17
|
+
case opcode & 3
|
|
18
|
+
when 0 then block_load(step, repeat)
|
|
19
|
+
when 1 then block_compare(step, repeat)
|
|
20
|
+
when 2 then block_input(step, repeat)
|
|
21
|
+
else block_output(step, repeat)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def block_load(step, repeat)
|
|
26
|
+
value = read_byte(hl)
|
|
27
|
+
write_byte(de, value)
|
|
28
|
+
@cycles += 2
|
|
29
|
+
self.hl = (hl + step) & 0xffff
|
|
30
|
+
self.de = (de + step) & 0xffff
|
|
31
|
+
self.bc = (bc - 1) & 0xffff
|
|
32
|
+
sum = value + @a
|
|
33
|
+
assign_flags((@f & (SF | ZF | CF)) | (sum & XF) | ((sum & 0x02) << 4) | (bc.zero? ? 0 : PF))
|
|
34
|
+
repeat_block if repeat && !bc.zero?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def block_compare(step, repeat)
|
|
38
|
+
value = read_byte(hl)
|
|
39
|
+
@cycles += 5
|
|
40
|
+
result = (@a - value) & 0xff
|
|
41
|
+
half = (@a ^ value ^ result) & HF
|
|
42
|
+
adjusted = result - (half >> 4)
|
|
43
|
+
self.hl = (hl + step) & 0xffff
|
|
44
|
+
self.bc = (bc - 1) & 0xffff
|
|
45
|
+
@wz = (@wz + step) & 0xffff
|
|
46
|
+
assign_flags((@f & CF) | NF | half | (SZ53[result] & (SF | ZF)) | (adjusted & XF) | ((adjusted & 0x02) << 4) |
|
|
47
|
+
(bc.zero? ? 0 : PF))
|
|
48
|
+
repeat_block if repeat && !bc.zero? && !result.zero?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def repeat_block
|
|
52
|
+
@cycles += 5
|
|
53
|
+
@pc = (@pc - 2) & 0xffff
|
|
54
|
+
@wz = (@pc + 1) & 0xffff
|
|
55
|
+
assign_flags((@f & ~XYF) | ((@pc >> 8) & XYF))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# The machine cycles that reach the bus. An opcode fetch (M1) takes 4
|
|
6
|
+
# T-states and refreshes a row of DRAM, which counts up the low 7 bits
|
|
7
|
+
# of R. A memory read or write takes 3, and an I/O read or write 4,
|
|
8
|
+
# one of them the wait state the Z80 inserts itself. Each calls the bus
|
|
9
|
+
# on its first T-state, and T-states the CPU spends inside lengthen
|
|
10
|
+
# the machine cycle before them.
|
|
11
|
+
module BusCycles
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def fetch_opcode
|
|
15
|
+
opcode = @bus.fetch(@pc)
|
|
16
|
+
@pc = (@pc + 1) & 0xffff
|
|
17
|
+
refresh
|
|
18
|
+
@cycles += 4
|
|
19
|
+
opcode
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A halted CPU fetches from the address after HALT, again and again,
|
|
23
|
+
# and leaves the program counter there.
|
|
24
|
+
def idle_fetch
|
|
25
|
+
@bus.fetch(@pc)
|
|
26
|
+
refresh
|
|
27
|
+
@cycles += 4
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def refresh
|
|
31
|
+
@r = (@r & 0x80) | ((@r + 1) & 0x7f)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read_byte(address)
|
|
35
|
+
value = @bus.read(address)
|
|
36
|
+
@cycles += 3
|
|
37
|
+
value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def write_byte(address, value)
|
|
41
|
+
@bus.write(address, value)
|
|
42
|
+
@cycles += 3
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def read_word(address)
|
|
46
|
+
low = read_byte(address)
|
|
47
|
+
low | (read_byte((address + 1) & 0xffff) << 8)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_word(address, value)
|
|
51
|
+
write_byte(address, value & 0xff)
|
|
52
|
+
write_byte((address + 1) & 0xffff, value >> 8)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def input(port)
|
|
56
|
+
value = @bus.input(port)
|
|
57
|
+
@cycles += 4
|
|
58
|
+
value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def output(port, value)
|
|
62
|
+
@bus.output(port, value)
|
|
63
|
+
@cycles += 4
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The byte after the opcode.
|
|
67
|
+
def fetch_byte
|
|
68
|
+
value = read_byte(@pc)
|
|
69
|
+
@pc = (@pc + 1) & 0xffff
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def fetch_word
|
|
74
|
+
low = fetch_byte
|
|
75
|
+
low | (fetch_byte << 8)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def push(value)
|
|
79
|
+
@sp = (@sp - 1) & 0xffff
|
|
80
|
+
write_byte(@sp, value >> 8)
|
|
81
|
+
@sp = (@sp - 1) & 0xffff
|
|
82
|
+
write_byte(@sp, value & 0xff)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def pop
|
|
86
|
+
low = read_byte(@sp)
|
|
87
|
+
high = read_byte((@sp + 1) & 0xffff)
|
|
88
|
+
@sp = (@sp + 2) & 0xffff
|
|
89
|
+
low | (high << 8)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The address of an (HL) operand, or of (IX+d), whose displacement is
|
|
93
|
+
# read from after the opcode and added in 5 T-states inside.
|
|
94
|
+
def operand_address
|
|
95
|
+
return hl if @prefix.zero?
|
|
96
|
+
|
|
97
|
+
displaced
|
|
98
|
+
@cycles += 5
|
|
99
|
+
@wz
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Reads the displacement of an (IX+d) or (IY+d) operand and leaves
|
|
103
|
+
# the address in WZ.
|
|
104
|
+
def displaced
|
|
105
|
+
offset = fetch_byte
|
|
106
|
+
offset -= 256 if offset > 127
|
|
107
|
+
@wz = (index_register + offset) & 0xffff
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/z80/flags"
|
|
4
|
+
require "badline/z80/registers"
|
|
5
|
+
require "badline/z80/bus_cycles"
|
|
6
|
+
require "badline/z80/arithmetic"
|
|
7
|
+
require "badline/z80/shifts"
|
|
8
|
+
require "badline/z80/loads"
|
|
9
|
+
require "badline/z80/exchanges"
|
|
10
|
+
require "badline/z80/jumps"
|
|
11
|
+
require "badline/z80/main_page"
|
|
12
|
+
require "badline/z80/bit_page"
|
|
13
|
+
require "badline/z80/extended_page"
|
|
14
|
+
require "badline/z80/block_transfers"
|
|
15
|
+
require "badline/z80/block_io"
|
|
16
|
+
require "badline/z80/interrupt_acceptance"
|
|
17
|
+
require "badline/z80/saved_state"
|
|
18
|
+
|
|
19
|
+
module Badline
|
|
20
|
+
class Z80
|
|
21
|
+
# The Z80 core, on whatever bus +bus+ is. The bus answers
|
|
22
|
+
# fetch(address) for an opcode fetch (M1), read(address) and
|
|
23
|
+
# write(address, value) for memory, input(port) and output(port, value)
|
|
24
|
+
# for I/O, and acknowledge for the byte a device puts on the data bus
|
|
25
|
+
# when the CPU takes a maskable interrupt.
|
|
26
|
+
#
|
|
27
|
+
# #step! runs one instruction, prefixes and all, or takes an interrupt,
|
|
28
|
+
# or idles one opcode fetch while halted. #cycles counts T-states, and
|
|
29
|
+
# each bus call happens while it holds the T-state its machine cycle
|
|
30
|
+
# starts on.
|
|
31
|
+
module Core
|
|
32
|
+
include Flags
|
|
33
|
+
include Registers
|
|
34
|
+
include BusCycles
|
|
35
|
+
include Arithmetic
|
|
36
|
+
include Shifts
|
|
37
|
+
include Loads
|
|
38
|
+
include Exchanges
|
|
39
|
+
include Jumps
|
|
40
|
+
include MainPage
|
|
41
|
+
include BitPage
|
|
42
|
+
include ExtendedPage
|
|
43
|
+
include BlockTransfers
|
|
44
|
+
include BlockIO
|
|
45
|
+
include InterruptAcceptance
|
|
46
|
+
include SavedState
|
|
47
|
+
|
|
48
|
+
attr_reader :bus, :cycles, :nmi
|
|
49
|
+
attr_accessor :int
|
|
50
|
+
|
|
51
|
+
def initialize(bus)
|
|
52
|
+
@bus = bus
|
|
53
|
+
@cycles = 0
|
|
54
|
+
@a = @f = 0xff
|
|
55
|
+
@b = @c = @d = @e = @h = @l = 0xff
|
|
56
|
+
@af_alt = @bc_alt = @de_alt = @hl_alt = 0xffff
|
|
57
|
+
@ix = @iy = @sp = 0xffff
|
|
58
|
+
@wz = @q = @last_q = 0
|
|
59
|
+
@prefix = 0
|
|
60
|
+
@int = @nmi = @nmi_pending = false
|
|
61
|
+
reset!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The RESET line: the program counter, I, R and the interrupt mode go
|
|
65
|
+
# to 0, and interrupts are disabled.
|
|
66
|
+
def reset!
|
|
67
|
+
@pc = @i = @r = @im = 0
|
|
68
|
+
@iff1 = @iff2 = false
|
|
69
|
+
@halted = @after_ei = @after_ld_a_ir = false
|
|
70
|
+
@nmi_pending = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The NMI line. Asserting it latches an interrupt, taken after the
|
|
74
|
+
# current instruction, and holding it does nothing more.
|
|
75
|
+
def nmi=(asserted)
|
|
76
|
+
@nmi_pending = true if asserted && !@nmi
|
|
77
|
+
@nmi = asserted
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The WAIT line: a bus that holds it during an access lengthens that
|
|
81
|
+
# machine cycle by +states+ T-states.
|
|
82
|
+
def wait(states)
|
|
83
|
+
@cycles += states
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def step!
|
|
87
|
+
if @nmi_pending
|
|
88
|
+
take_nmi
|
|
89
|
+
elsif @int && @iff1 && !@after_ei
|
|
90
|
+
take_int
|
|
91
|
+
else
|
|
92
|
+
@after_ei = @after_ld_a_ir = false
|
|
93
|
+
@last_q = @q
|
|
94
|
+
@q = 0
|
|
95
|
+
@prefix = 0
|
|
96
|
+
@halted ? idle_fetch : execute(fetch_opcode)
|
|
97
|
+
end
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def inspect
|
|
102
|
+
"Cycles: #{@cycles}, PC: #{hex(@pc)}, SP: #{hex(@sp)}, AF: #{hex(af)}, BC: #{hex(bc)}, " \
|
|
103
|
+
"DE: #{hex(de)}, HL: #{hex(hl)}, IX: #{hex(@ix)}, IY: #{hex(@iy)}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def hex(value)
|
|
109
|
+
value.to_s(16).rjust(4, "0")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# EX AF,AF', EXX, EX DE,HL and EX (SP),HL. Only the last swaps IX or IY
|
|
6
|
+
# after a prefix.
|
|
7
|
+
module Exchanges
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def exchange_alternates
|
|
11
|
+
bc_alt = @bc_alt
|
|
12
|
+
de_alt = @de_alt
|
|
13
|
+
hl_alt = @hl_alt
|
|
14
|
+
@bc_alt = bc
|
|
15
|
+
@de_alt = de
|
|
16
|
+
@hl_alt = hl
|
|
17
|
+
self.bc = bc_alt
|
|
18
|
+
self.de = de_alt
|
|
19
|
+
self.hl = hl_alt
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def exchange_af
|
|
23
|
+
af_alt = @af_alt
|
|
24
|
+
@af_alt = af
|
|
25
|
+
self.af = af_alt
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def exchange_de_hl
|
|
29
|
+
d = @d
|
|
30
|
+
e = @e
|
|
31
|
+
@d = @h
|
|
32
|
+
@e = @l
|
|
33
|
+
@h = d
|
|
34
|
+
@l = e
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def exchange_stack_top
|
|
38
|
+
low = read_byte(@sp)
|
|
39
|
+
high = read_byte((@sp + 1) & 0xffff)
|
|
40
|
+
@cycles += 1
|
|
41
|
+
value = hl_or_index
|
|
42
|
+
write_byte((@sp + 1) & 0xffff, value >> 8)
|
|
43
|
+
write_byte(@sp, value & 0xff)
|
|
44
|
+
@cycles += 2
|
|
45
|
+
@wz = low | (high << 8)
|
|
46
|
+
self.hl_or_index = @wz
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|