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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +24 -6
  4. data/CONTRIBUTING.md +15 -9
  5. data/README.md +16 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/c128/bus.rb +4 -17
  10. data/lib/badline/c128/drives.rb +108 -0
  11. data/lib/badline/c128/fast_serial.rb +63 -0
  12. data/lib/badline/c128/io_pages.rb +35 -0
  13. data/lib/badline/c128/mmu.rb +24 -7
  14. data/lib/badline/c128/modes.rb +17 -4
  15. data/lib/badline/c128/saved_state.rb +23 -0
  16. data/lib/badline/c128/z80.rb +49 -0
  17. data/lib/badline/c128/z80_pages.rb +118 -0
  18. data/lib/badline/c128/z80_turns.rb +106 -0
  19. data/lib/badline/c128.rb +25 -26
  20. data/lib/badline/cartridge/action_replay.rb +2 -2
  21. data/lib/badline/cartridge/comal80.rb +1 -7
  22. data/lib/badline/cartridge/dinamic.rb +0 -8
  23. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  24. data/lib/badline/cartridge/fun_play.rb +0 -8
  25. data/lib/badline/cartridge/game_system.rb +0 -8
  26. data/lib/badline/cartridge/kcs_power.rb +10 -7
  27. data/lib/badline/cartridge/mach5.rb +2 -4
  28. data/lib/badline/cartridge/magic_desk.rb +0 -8
  29. data/lib/badline/cartridge/ocean.rb +0 -8
  30. data/lib/badline/cartridge/pagefox.rb +2 -2
  31. data/lib/badline/cartridge/retro_replay.rb +7 -6
  32. data/lib/badline/cartridge/rex_utility.rb +2 -6
  33. data/lib/badline/cartridge/rgcd.rb +2 -2
  34. data/lib/badline/cartridge/simons_basic.rb +0 -5
  35. data/lib/badline/cartridge/super_games.rb +4 -2
  36. data/lib/badline/cartridge/westermann.rb +2 -6
  37. data/lib/badline/cartridge.rb +37 -9
  38. data/lib/badline/cia/fast_forward.rb +60 -0
  39. data/lib/badline/cia/serial.rb +12 -0
  40. data/lib/badline/cia/timer.rb +39 -0
  41. data/lib/badline/cia.rb +10 -5
  42. data/lib/badline/computer/attachments.rb +4 -50
  43. data/lib/badline/computer/true_drives.rb +80 -0
  44. data/lib/badline/computer.rb +4 -0
  45. data/lib/badline/core.rb +2 -0
  46. data/lib/badline/drive/core.rb +147 -0
  47. data/lib/badline/drive/fast_serial.rb +73 -0
  48. data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
  49. data/lib/badline/drive/idle_cpu.rb +28 -0
  50. data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
  51. data/lib/badline/drive/serial_lines.rb +73 -0
  52. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
  53. data/lib/badline/drive/vias.rb +84 -0
  54. data/lib/badline/drive/watch.rb +150 -0
  55. data/lib/badline/drive.rb +11 -0
  56. data/lib/badline/drive1541/bus.rb +5 -129
  57. data/lib/badline/drive1541/byte_ready.rb +48 -0
  58. data/lib/badline/drive1541/cpu.rb +1 -19
  59. data/lib/badline/drive1541/disk.rb +77 -20
  60. data/lib/badline/drive1541/disk_state.rb +7 -2
  61. data/lib/badline/drive1541/disk_via.rb +3 -0
  62. data/lib/badline/drive1541/mechanism.rb +48 -14
  63. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  64. data/lib/badline/drive1541/rotation.rb +5 -5
  65. data/lib/badline/drive1541/serial_port.rb +4 -60
  66. data/lib/badline/drive1541.rb +14 -172
  67. data/lib/badline/drive1571/bus.rb +111 -0
  68. data/lib/badline/drive1571/cpu.rb +11 -0
  69. data/lib/badline/drive1571/serial_port.rb +37 -0
  70. data/lib/badline/drive1571/serial_via.rb +25 -0
  71. data/lib/badline/drive1571/wd1770.rb +120 -0
  72. data/lib/badline/drive1571.rb +175 -0
  73. data/lib/badline/drive1581/bus.rb +137 -0
  74. data/lib/badline/drive1581/cpu.rb +11 -0
  75. data/lib/badline/drive1581/disk.rb +135 -0
  76. data/lib/badline/drive1581/disk_state.rb +87 -0
  77. data/lib/badline/drive1581/mechanism.rb +117 -0
  78. data/lib/badline/drive1581/serial_port.rb +52 -0
  79. data/lib/badline/drive1581/slot.rb +78 -0
  80. data/lib/badline/drive1581/track.rb +100 -0
  81. data/lib/badline/drive1581/track_bytes.rb +75 -0
  82. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  83. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  84. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  85. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  86. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  87. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  88. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  89. data/lib/badline/drive1581/wd1772.rb +216 -0
  90. data/lib/badline/drive1581.rb +233 -0
  91. data/lib/badline/frontend/app.rb +2 -4
  92. data/lib/badline/frontend/audio_device.rb +67 -0
  93. data/lib/badline/frontend/audio_sink.rb +16 -50
  94. data/lib/badline/frontend/boot.rb +18 -10
  95. data/lib/badline/frontend/buttons.rb +7 -7
  96. data/lib/badline/frontend/drive_led.rb +1 -1
  97. data/lib/badline/frontend/keys.rb +33 -8
  98. data/lib/badline/frontend/media_slots.rb +42 -0
  99. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  100. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  101. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  102. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  103. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
  104. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  105. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  106. data/lib/badline/frontend/menu/theme.rb +18 -0
  107. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  108. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  109. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  110. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  111. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  112. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  113. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  114. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  115. data/lib/badline/frontend/player/theme.rb +18 -0
  116. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  117. data/lib/badline/frontend/sound.rb +19 -38
  118. data/lib/badline/frontend/timeline.rb +3 -14
  119. data/lib/badline/frontend.rb +24 -20
  120. data/lib/badline/iec_bus.rb +68 -6
  121. data/lib/badline/machine.rb +9 -6
  122. data/lib/badline/media/boot_disk.rb +50 -0
  123. data/lib/badline/media/not_disk.rb +8 -0
  124. data/lib/badline/media/true_drive.rb +48 -25
  125. data/lib/badline/media.rb +9 -3
  126. data/lib/badline/options/event.rb +2 -2
  127. data/lib/badline/options/family.rb +3 -9
  128. data/lib/badline/options/help.rb +3 -1
  129. data/lib/badline/options/table.rb +1 -1
  130. data/lib/badline/roms/README +18 -0
  131. data/lib/badline/roms/dos1571.rom +0 -0
  132. data/lib/badline/roms/dos1581.rom +0 -0
  133. data/lib/badline/sdl.rb +37 -20
  134. data/lib/badline/snapshot/state.rb +5 -4
  135. data/lib/badline/storage/d64_image.rb +3 -0
  136. data/lib/badline/storage/d71_image.rb +9 -4
  137. data/lib/badline/storage/g64_image.rb +7 -2
  138. data/lib/badline/storage/p00.rb +8 -0
  139. data/lib/badline/time_of_day.rb +9 -0
  140. data/lib/badline/version.rb +1 -1
  141. data/lib/badline/via/fast_forward.rb +3 -3
  142. data/lib/badline/vic20/attachments.rb +23 -3
  143. data/lib/badline/vic20/saved_state.rb +2 -0
  144. data/lib/badline/vic20.rb +3 -0
  145. data/lib/badline/z80/arithmetic.rb +117 -0
  146. data/lib/badline/z80/bit_page.rb +55 -0
  147. data/lib/badline/z80/block_io.rb +58 -0
  148. data/lib/badline/z80/block_transfers.rb +59 -0
  149. data/lib/badline/z80/bus_cycles.rb +111 -0
  150. data/lib/badline/z80/core.rb +113 -0
  151. data/lib/badline/z80/exchanges.rb +50 -0
  152. data/lib/badline/z80/extended_page.rb +89 -0
  153. data/lib/badline/z80/flags.rb +37 -0
  154. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  155. data/lib/badline/z80/jumps.rb +82 -0
  156. data/lib/badline/z80/loads.rb +98 -0
  157. data/lib/badline/z80/main_page.rb +134 -0
  158. data/lib/badline/z80/registers.rb +120 -0
  159. data/lib/badline/z80/saved_state.rb +69 -0
  160. data/lib/badline/z80/shifts.rb +64 -0
  161. data/lib/badline/z80.rb +11 -0
  162. metadata +89 -24
  163. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  164. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  165. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  166. /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 Drive1541::Idle.
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 Drive1541::Idle.
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 Drive1541::Idle) has to bring back: those
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.device = nil
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
- @drive1541&.device == KernalTrap::Routine::DEVICE ? nil : KernalTrap::Routine::DEVICE
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