badline 0.10.1 → 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 +23 -0
- data/CLAUDE.md +25 -6
- data/CONTRIBUTING.md +8 -3
- data/README.md +63 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +197 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +121 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +322 -0
- data/lib/badline/chrout_trap.rb +3 -0
- 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 +10 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -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 +35 -22
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +8 -2
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +20 -9
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +7 -2
- data/lib/badline/options/table.rb +8 -2
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +36 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- 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/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- 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
- data/lib/badline.rb +1 -0
- metadata +59 -4
|
@@ -68,6 +68,12 @@ module Badline
|
|
|
68
68
|
# True once AEC has followed BA down and the VIC owns the bus.
|
|
69
69
|
def bus_taken?(column) = @ba ? column >= @ba + BA_DELAY : false
|
|
70
70
|
|
|
71
|
+
# The CPU kept the bus through the cycle before +column+, so on a bad
|
|
72
|
+
# line AEC falls BA_DELAY cycles after it at the earliest.
|
|
73
|
+
def keep_bus(column)
|
|
74
|
+
@ba &&= column
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
def fetching?(column)
|
|
72
78
|
@matched && column >= FETCH_FIRST && column <= DMA_LAST
|
|
73
79
|
end
|
|
@@ -98,6 +104,12 @@ module Badline
|
|
|
98
104
|
|
|
99
105
|
def new_line(line)
|
|
100
106
|
@ba = nil
|
|
107
|
+
raster_step(line)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The raster counter moved on to +line+, at the line's start or, under
|
|
111
|
+
# the VIC-IIe's TEST bit, within it.
|
|
112
|
+
def raster_step(line)
|
|
101
113
|
@line_bits = line & 0b111
|
|
102
114
|
@den_line = line == FIRST_LINE
|
|
103
115
|
@in_window = line.between?(FIRST_LINE, LAST_LINE)
|
|
@@ -150,6 +150,9 @@ module Badline
|
|
|
150
150
|
def fast? = @iie && @bytes[0x30].anybits?(0x01)
|
|
151
151
|
def test? = @iie && @bytes[0x30].anybits?(0x02)
|
|
152
152
|
|
|
153
|
+
# FAST and TEST as bits 0 and 1.
|
|
154
|
+
def clock_bits = @iie ? @bytes[0x30] & 0x03 : 0
|
|
155
|
+
|
|
153
156
|
def raster_target = uint16(@bytes[0x12], (@bytes[0x11] & 0x80) >> 7)
|
|
154
157
|
|
|
155
158
|
def latch_irq!(bit)
|
data/lib/badline/vic.rb
CHANGED
|
@@ -42,6 +42,11 @@ module Badline
|
|
|
42
42
|
# GREEN_PALETTE on a monochrome monitor.
|
|
43
43
|
attr_accessor :palette
|
|
44
44
|
|
|
45
|
+
# The line of #display the beam draws, which is the raster line unless
|
|
46
|
+
# the VIC-IIe's TEST bit has moved the raster counter on since the
|
|
47
|
+
# last vertical sync (#test_step!).
|
|
48
|
+
attr_reader :output_line
|
|
49
|
+
|
|
45
50
|
# The parts a VICE snapshot reads and sets.
|
|
46
51
|
attr_reader :registers, :display_state, :sequencer, :sprites, :character_buffer, :color_buffer,
|
|
47
52
|
:fetch_d011
|
|
@@ -86,6 +91,11 @@ module Badline
|
|
|
86
91
|
HOOK_DISPLAY = 16
|
|
87
92
|
HOOK_LAST = 32
|
|
88
93
|
|
|
94
|
+
# The vertical sync starts this many lines into the vertical blank,
|
|
95
|
+
# and lasts VSYNC_LINES.
|
|
96
|
+
VSYNC_DELAY = 3
|
|
97
|
+
VSYNC_LINES = 3
|
|
98
|
+
|
|
89
99
|
# On the 6569 the DMA compares fall in columns 53 and 54 and the
|
|
90
100
|
# display compare in column 57. A region whose sprite fetches run later
|
|
91
101
|
# moves them with the fetches (Region::Profile#sprite_cycle).
|
|
@@ -125,6 +135,7 @@ module Badline
|
|
|
125
135
|
@height = region.lines_per_frame
|
|
126
136
|
@last_column = @columns_per_line - 1
|
|
127
137
|
@last_line = @height - 1
|
|
138
|
+
@vsync_line = region.vblank[0] + VSYNC_DELAY
|
|
128
139
|
layout_columns
|
|
129
140
|
@blank_columns = Array.new(@columns_per_line) { |column| Region.blanked?(column, region.hblank) }.freeze
|
|
130
141
|
@blank_lines = Array.new(@height) { |line| Region.blanked?(line, region.vblank) }.freeze
|
|
@@ -153,6 +164,7 @@ module Badline
|
|
|
153
164
|
|
|
154
165
|
@column = 0
|
|
155
166
|
@rasterline = 0
|
|
167
|
+
@output_line = 0
|
|
156
168
|
|
|
157
169
|
@character_buffer = Array.new(40, 0)
|
|
158
170
|
@color_buffer = Array.new(40, 0)
|
|
@@ -169,11 +181,13 @@ module Badline
|
|
|
169
181
|
@lp_triggered = false
|
|
170
182
|
@lp_low = false
|
|
171
183
|
@raster_match = false
|
|
184
|
+
@test_wrap = false
|
|
172
185
|
end
|
|
173
186
|
|
|
174
187
|
# Everything the VIC holds between cycles: the beam, the fetch and
|
|
175
188
|
# g-access pipeline, the light pen and raster latches, the finished
|
|
176
|
-
# lines, the
|
|
189
|
+
# lines, on the VIC-IIe the display line and the TEST bit's wrap, the
|
|
190
|
+
# registers and the display, sequencer and sprite logic.
|
|
177
191
|
# Whether it renders is the host's, and every line comes back dirty so
|
|
178
192
|
# a front end repaints.
|
|
179
193
|
def save_state(out)
|
|
@@ -184,6 +198,7 @@ module Badline
|
|
|
184
198
|
out.int(@vic_bank.lines)
|
|
185
199
|
out.blob(@character_buffer).blob(@color_buffer).booleans(@sprite_ba)
|
|
186
200
|
@lines.each { |line| out.blob(line) }
|
|
201
|
+
out.int(@output_line).boolean(@test_wrap) if @iie
|
|
187
202
|
@registers.save_state(out)
|
|
188
203
|
@display_state.save_state(out)
|
|
189
204
|
@sequencer.save_state(out)
|
|
@@ -195,6 +210,7 @@ module Badline
|
|
|
195
210
|
load_beam(input)
|
|
196
211
|
load_buffers(input)
|
|
197
212
|
load_lines(input)
|
|
213
|
+
load_test_state(input) if @iie
|
|
198
214
|
@registers.load_state(input)
|
|
199
215
|
@display_state.load_state(input)
|
|
200
216
|
@sequencer.load_state(input)
|
|
@@ -218,6 +234,7 @@ module Badline
|
|
|
218
234
|
if @column == @columns_per_line
|
|
219
235
|
finish_line!
|
|
220
236
|
@column = 0
|
|
237
|
+
step_output_line
|
|
221
238
|
@rasterline = @rasterline == @last_line ? 0 : @rasterline + 1
|
|
222
239
|
check_raster_irq!(@rasterline.zero? ? @last_line : @rasterline)
|
|
223
240
|
end
|
|
@@ -255,6 +272,57 @@ module Badline
|
|
|
255
272
|
def fast? = @registers.fast?
|
|
256
273
|
def test? = @registers.test?
|
|
257
274
|
|
|
275
|
+
# $D030's FAST and TEST bits as bits 0 and 1, which the C128 reads once
|
|
276
|
+
# a cycle.
|
|
277
|
+
def clock_bits = @registers.clock_bits
|
|
278
|
+
|
|
279
|
+
# Whether the cycle just run was one of the five refresh cycles, Bauer's
|
|
280
|
+
# 11-15, whose phi1 half the VIC keeps in the VIC-IIe's FAST mode.
|
|
281
|
+
# @column has already advanced, so it is the Bauer cycle less one.
|
|
282
|
+
def refresh_cycle? = @column.between?(10, 14)
|
|
283
|
+
|
|
284
|
+
# In the VIC-IIe's FAST mode the 8502 drives the bus in both halves of
|
|
285
|
+
# the cycle, and the VIC's accesses in the cycle just run latched what
|
|
286
|
+
# it put there. The g-access, or the idle access, took the byte of
|
|
287
|
+
# phi1. A c-access took the byte of phi2 as one made before AEC does
|
|
288
|
+
# (#fetch_character_data!): $ff for the video matrix, unless the CPU
|
|
289
|
+
# was reading or writing the VIC's own registers, and the byte's low
|
|
290
|
+
# nibble for colour. AEC can't follow BA down while the CPU runs at
|
|
291
|
+
# 2 MHz, so on a bad line it falls three cycles after FAST mode ends.
|
|
292
|
+
def take_cpu_bus(phi1, phi2, register_access)
|
|
293
|
+
slot = @g_tick
|
|
294
|
+
@g_data[slot] = phi1 unless @g_kind[slot] == G_BLANK
|
|
295
|
+
display_state = @display_state
|
|
296
|
+
display_state.keep_bus(@column)
|
|
297
|
+
return unless display_state.fetching?(@column.zero? ? @last_column : @column - 1)
|
|
298
|
+
|
|
299
|
+
vmli = display_state.vmli
|
|
300
|
+
@character_buffer[vmli] = register_access ? phi2 : 0xff
|
|
301
|
+
@color_buffer[vmli] = phi2 & 0x0f
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# The VIC-IIe's TEST bit clocks the raster counter in every cycle, and
|
|
305
|
+
# the C128 calls this ahead of each cycle the bit is set in. The line's
|
|
306
|
+
# last cycle, whose own step it falls in with, adds nothing more. The
|
|
307
|
+
# counter takes two steps from the frame's last line to line 0, as line
|
|
308
|
+
# 0 reads the last line for its first cycle.
|
|
309
|
+
def test_step!
|
|
310
|
+
return if @column == @last_column
|
|
311
|
+
|
|
312
|
+
if @rasterline == @last_line
|
|
313
|
+
@test_wrap = !@test_wrap
|
|
314
|
+
return if @test_wrap
|
|
315
|
+
|
|
316
|
+
@rasterline = 0
|
|
317
|
+
@display_state.new_frame
|
|
318
|
+
else
|
|
319
|
+
@test_wrap = false
|
|
320
|
+
@rasterline += 1
|
|
321
|
+
end
|
|
322
|
+
@display_state.raster_step(@rasterline)
|
|
323
|
+
check_raster_irq!
|
|
324
|
+
end
|
|
325
|
+
|
|
258
326
|
def peek(addr)
|
|
259
327
|
i = offset_of(addr) % (2**6)
|
|
260
328
|
case i
|
|
@@ -363,7 +431,7 @@ module Badline
|
|
|
363
431
|
|
|
364
432
|
# Puts the beam at the start of `line`.
|
|
365
433
|
def restore_line(line)
|
|
366
|
-
@rasterline = line
|
|
434
|
+
@rasterline = @output_line = line
|
|
367
435
|
@column = 0
|
|
368
436
|
end
|
|
369
437
|
|
|
@@ -400,7 +468,7 @@ module Badline
|
|
|
400
468
|
|
|
401
469
|
def load_beam(input)
|
|
402
470
|
@column = input.int
|
|
403
|
-
@rasterline = input.int
|
|
471
|
+
@rasterline = @output_line = input.int
|
|
404
472
|
@cycles = input.int
|
|
405
473
|
@pending_write = input.boolean?
|
|
406
474
|
@g_tick = input.int
|
|
@@ -421,6 +489,13 @@ module Badline
|
|
|
421
489
|
input.booleans_into(@sprite_ba)
|
|
422
490
|
end
|
|
423
491
|
|
|
492
|
+
# The VIC-IIe's display line and TEST wrap, which only its TEST bit
|
|
493
|
+
# moves off the raster line.
|
|
494
|
+
def load_test_state(input)
|
|
495
|
+
@output_line = input.int
|
|
496
|
+
@test_wrap = input.boolean?
|
|
497
|
+
end
|
|
498
|
+
|
|
424
499
|
# The display is the finished lines one after another.
|
|
425
500
|
def load_lines(input)
|
|
426
501
|
@lines.each_with_index do |line, number|
|
|
@@ -663,7 +738,7 @@ module Badline
|
|
|
663
738
|
# or not there is a line to draw — then composite the active sprites
|
|
664
739
|
# over the finished background and copy the line into the frame display.
|
|
665
740
|
def finish_line!
|
|
666
|
-
return
|
|
741
|
+
return finish_blank_line if vblank? || !@render
|
|
667
742
|
|
|
668
743
|
composite = @sprites.active?
|
|
669
744
|
@sequencer.apply_color_patches
|
|
@@ -672,12 +747,38 @@ module Badline
|
|
|
672
747
|
@sequencer.apply_border if composite
|
|
673
748
|
|
|
674
749
|
colors = @sequencer.colors
|
|
675
|
-
|
|
750
|
+
output_line = @output_line
|
|
751
|
+
line = @lines[output_line]
|
|
676
752
|
return if line == colors
|
|
677
753
|
|
|
678
754
|
line[0, @width] = colors
|
|
679
|
-
@display[
|
|
680
|
-
@dirty_lines[
|
|
755
|
+
@display[output_line * @width, @width] = colors
|
|
756
|
+
@dirty_lines[output_line] = true
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
# A blanked line paints nothing, and is never shown unless the TEST
|
|
760
|
+
# bit has moved the beam off the raster line, where it shows black.
|
|
761
|
+
def finish_blank_line
|
|
762
|
+
@sprites.finish_line(nil, @sequencer.fg)
|
|
763
|
+
return if @output_line == @rasterline || !@render
|
|
764
|
+
|
|
765
|
+
line = @output_line
|
|
766
|
+
@lines[line].fill(0)
|
|
767
|
+
@display.fill(0, line * @width, @width)
|
|
768
|
+
@dirty_lines[line] = true
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
# The display line the beam draws next follows the one before it, and
|
|
772
|
+
# a line that ends in the vertical sync puts it back on the raster
|
|
773
|
+
# line. Only the VIC-IIe's TEST bit moves the raster counter on between
|
|
774
|
+
# the two, so the lines it skips shift the picture up until the next
|
|
775
|
+
# sync.
|
|
776
|
+
def step_output_line
|
|
777
|
+
@output_line = if @rasterline.between?(@vsync_line, @vsync_line + VSYNC_LINES - 1)
|
|
778
|
+
@rasterline + 1
|
|
779
|
+
else
|
|
780
|
+
@output_line == @last_line ? 0 : @output_line + 1
|
|
781
|
+
end
|
|
681
782
|
end
|
|
682
783
|
|
|
683
784
|
def video_matrix(index)
|
|
@@ -75,6 +75,12 @@ module Badline
|
|
|
75
75
|
@serial_trap&.device = serial_trap_device
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# The true drive on the serial bus: the Drive1541, or nil.
|
|
79
|
+
def true_drive = @drive1541
|
|
80
|
+
|
|
81
|
+
# Plugs a Drive1541 in as device 8, and returns it.
|
|
82
|
+
def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
83
|
+
|
|
78
84
|
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
79
85
|
def detach_drive1541
|
|
80
86
|
return unless @drive1541
|
|
@@ -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
|