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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/CLAUDE.md +25 -6
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +63 -4
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +15 -0
  8. data/lib/badline/address_bus/pla.rb +3 -16
  9. data/lib/badline/address_bus.rb +14 -0
  10. data/lib/badline/c128/banks.rb +35 -0
  11. data/lib/badline/c128/bus.rb +347 -0
  12. data/lib/badline/c128/bus_state.rb +53 -0
  13. data/lib/badline/c128/color_lines.rb +39 -0
  14. data/lib/badline/c128/cpu.rb +10 -0
  15. data/lib/badline/c128/drives.rb +95 -0
  16. data/lib/badline/c128/fast_serial.rb +62 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +197 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +121 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +322 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/cia/fast_forward.rb +42 -0
  31. data/lib/badline/cia/serial.rb +12 -0
  32. data/lib/badline/cia/timer.rb +39 -0
  33. data/lib/badline/cia.rb +2 -0
  34. data/lib/badline/computer/attachments.rb +10 -13
  35. data/lib/badline/computer/kernal_traps.rb +29 -0
  36. data/lib/badline/computer.rb +5 -0
  37. data/lib/badline/drive/core.rb +172 -0
  38. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  39. data/lib/badline/drive/idle_cpu.rb +28 -0
  40. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  41. data/lib/badline/drive/serial_lines.rb +73 -0
  42. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  43. data/lib/badline/drive/watch.rb +150 -0
  44. data/lib/badline/drive.rb +9 -0
  45. data/lib/badline/drive1541/bus.rb +5 -129
  46. data/lib/badline/drive1541/byte_ready.rb +48 -0
  47. data/lib/badline/drive1541/cpu.rb +1 -19
  48. data/lib/badline/drive1541/disk.rb +77 -20
  49. data/lib/badline/drive1541/disk_state.rb +7 -2
  50. data/lib/badline/drive1541/disk_via.rb +3 -0
  51. data/lib/badline/drive1541/mechanism.rb +48 -14
  52. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  53. data/lib/badline/drive1541/rotation.rb +5 -5
  54. data/lib/badline/drive1541/serial_port.rb +4 -60
  55. data/lib/badline/drive1541.rb +13 -172
  56. data/lib/badline/drive1571/bus.rb +111 -0
  57. data/lib/badline/drive1571/cpu.rb +11 -0
  58. data/lib/badline/drive1571/serial_port.rb +37 -0
  59. data/lib/badline/drive1571/serial_via.rb +25 -0
  60. data/lib/badline/drive1571/wd1770.rb +34 -0
  61. data/lib/badline/drive1571.rb +225 -0
  62. data/lib/badline/frontend/app.rb +35 -22
  63. data/lib/badline/frontend/boot.rb +31 -1
  64. data/lib/badline/frontend/controls.rb +11 -0
  65. data/lib/badline/frontend/drive_led.rb +1 -1
  66. data/lib/badline/frontend/keys.rb +28 -1
  67. data/lib/badline/frontend/machine_window.rb +16 -13
  68. data/lib/badline/frontend/menu_media.rb +3 -3
  69. data/lib/badline/frontend/menu_pages.rb +8 -2
  70. data/lib/badline/frontend/menu_ports.rb +1 -1
  71. data/lib/badline/frontend/pause_menu.rb +1 -0
  72. data/lib/badline/frontend/screen.rb +33 -8
  73. data/lib/badline/frontend/screens.rb +90 -0
  74. data/lib/badline/frontend/timeline.rb +15 -4
  75. data/lib/badline/frontend.rb +1 -0
  76. data/lib/badline/iec_bus.rb +54 -4
  77. data/lib/badline/kernal_trap/file.rb +1 -1
  78. data/lib/badline/kernal_trap/layout.rb +58 -8
  79. data/lib/badline/kernal_trap/load.rb +1 -1
  80. data/lib/badline/kernal_trap/save.rb +1 -1
  81. data/lib/badline/kernal_trap/serial.rb +5 -0
  82. data/lib/badline/machine.rb +20 -9
  83. data/lib/badline/media/true_drive.rb +26 -23
  84. data/lib/badline/media.rb +8 -6
  85. data/lib/badline/options/event.rb +5 -1
  86. data/lib/badline/options/family.rb +52 -0
  87. data/lib/badline/options/help.rb +7 -2
  88. data/lib/badline/options/table.rb +8 -2
  89. data/lib/badline/options/validation.rb +14 -1
  90. data/lib/badline/options.rb +20 -19
  91. data/lib/badline/roms/README +36 -0
  92. data/lib/badline/roms/c128/basic.rom +0 -0
  93. data/lib/badline/roms/c128/character.rom +0 -0
  94. data/lib/badline/roms/c128/kernal.rom +0 -0
  95. data/lib/badline/roms/dos1571.rom +0 -0
  96. data/lib/badline/snapshot/container.rb +2 -0
  97. data/lib/badline/snapshot/image.rb +14 -0
  98. data/lib/badline/snapshot.rb +9 -2
  99. data/lib/badline/storage/d64_image.rb +3 -0
  100. data/lib/badline/storage/d71_image.rb +9 -4
  101. data/lib/badline/storage/g64_image.rb +7 -2
  102. data/lib/badline/time_of_day.rb +9 -0
  103. data/lib/badline/version.rb +1 -1
  104. data/lib/badline/via/fast_forward.rb +3 -3
  105. data/lib/badline/vic/display_state.rb +12 -0
  106. data/lib/badline/vic/registers.rb +3 -0
  107. data/lib/badline/vic.rb +108 -7
  108. data/lib/badline/vic20/attachments.rb +6 -0
  109. data/lib/badline/z80/arithmetic.rb +117 -0
  110. data/lib/badline/z80/bit_page.rb +55 -0
  111. data/lib/badline/z80/block_io.rb +58 -0
  112. data/lib/badline/z80/block_transfers.rb +59 -0
  113. data/lib/badline/z80/bus_cycles.rb +111 -0
  114. data/lib/badline/z80/core.rb +111 -0
  115. data/lib/badline/z80/exchanges.rb +50 -0
  116. data/lib/badline/z80/extended_page.rb +89 -0
  117. data/lib/badline/z80/flags.rb +37 -0
  118. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  119. data/lib/badline/z80/jumps.rb +82 -0
  120. data/lib/badline/z80/loads.rb +98 -0
  121. data/lib/badline/z80/main_page.rb +134 -0
  122. data/lib/badline/z80/registers.rb +120 -0
  123. data/lib/badline/z80/shifts.rb +64 -0
  124. data/lib/badline/z80.rb +11 -0
  125. data/lib/badline.rb +1 -0
  126. 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 registers and the display, sequencer and sprite logic.
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 @sprites.finish_line(nil, @sequencer.fg) if vblank? || !@render
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
- line = @lines[@rasterline]
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[@rasterline * @width, @width] = colors
680
- @dirty_lines[@rasterline] = true
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