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
@@ -0,0 +1,111 @@
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
+
18
+ module Badline
19
+ class Z80
20
+ # The Z80 core, on whatever bus +bus+ is. The bus answers
21
+ # fetch(address) for an opcode fetch (M1), read(address) and
22
+ # write(address, value) for memory, input(port) and output(port, value)
23
+ # for I/O, and acknowledge for the byte a device puts on the data bus
24
+ # when the CPU takes a maskable interrupt.
25
+ #
26
+ # #step! runs one instruction, prefixes and all, or takes an interrupt,
27
+ # or idles one opcode fetch while halted. #cycles counts T-states, and
28
+ # each bus call happens while it holds the T-state its machine cycle
29
+ # starts on.
30
+ module Core
31
+ include Flags
32
+ include Registers
33
+ include BusCycles
34
+ include Arithmetic
35
+ include Shifts
36
+ include Loads
37
+ include Exchanges
38
+ include Jumps
39
+ include MainPage
40
+ include BitPage
41
+ include ExtendedPage
42
+ include BlockTransfers
43
+ include BlockIO
44
+ include InterruptAcceptance
45
+
46
+ attr_reader :bus, :cycles, :nmi
47
+ attr_accessor :int
48
+
49
+ def initialize(bus)
50
+ @bus = bus
51
+ @cycles = 0
52
+ @a = @f = 0xff
53
+ @b = @c = @d = @e = @h = @l = 0xff
54
+ @af_alt = @bc_alt = @de_alt = @hl_alt = 0xffff
55
+ @ix = @iy = @sp = 0xffff
56
+ @wz = @q = @last_q = 0
57
+ @prefix = 0
58
+ @int = @nmi = @nmi_pending = false
59
+ reset!
60
+ end
61
+
62
+ # The RESET line: the program counter, I, R and the interrupt mode go
63
+ # to 0, and interrupts are disabled.
64
+ def reset!
65
+ @pc = @i = @r = @im = 0
66
+ @iff1 = @iff2 = false
67
+ @halted = @after_ei = @after_ld_a_ir = false
68
+ @nmi_pending = false
69
+ end
70
+
71
+ # The NMI line. Asserting it latches an interrupt, taken after the
72
+ # current instruction, and holding it does nothing more.
73
+ def nmi=(asserted)
74
+ @nmi_pending = true if asserted && !@nmi
75
+ @nmi = asserted
76
+ end
77
+
78
+ # The WAIT line: a bus that holds it during an access lengthens that
79
+ # machine cycle by +states+ T-states.
80
+ def wait(states)
81
+ @cycles += states
82
+ end
83
+
84
+ def step!
85
+ if @nmi_pending
86
+ take_nmi
87
+ elsif @int && @iff1 && !@after_ei
88
+ take_int
89
+ else
90
+ @after_ei = @after_ld_a_ir = false
91
+ @last_q = @q
92
+ @q = 0
93
+ @prefix = 0
94
+ @halted ? idle_fetch : execute(fetch_opcode)
95
+ end
96
+ nil
97
+ end
98
+
99
+ def inspect
100
+ "Cycles: #{@cycles}, PC: #{hex(@pc)}, SP: #{hex(@sp)}, AF: #{hex(af)}, BC: #{hex(bc)}, " \
101
+ "DE: #{hex(de)}, HL: #{hex(hl)}, IX: #{hex(@ix)}, IY: #{hex(@iy)}"
102
+ end
103
+
104
+ private
105
+
106
+ def hex(value)
107
+ value.to_s(16).rjust(4, "0")
108
+ end
109
+ end
110
+ end
111
+ 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
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ # The interrupt mode each of ED 46, 4E, 56, 5E, 66, 6E, 76 and 7E sets.
6
+ # 4E and 6E set mode 0 on the NMOS Z80.
7
+ INTERRUPT_MODES = [0, 0, 1, 2, 0, 0, 1, 2].freeze
8
+
9
+ # The ED page: its second quarter, the block instructions, and 8
10
+ # T-states of nothing for the rest.
11
+ module ExtendedPage
12
+ private
13
+
14
+ def execute_extended(opcode)
15
+ case opcode >> 6
16
+ when 1 then extended_operation(opcode)
17
+ when 2 then block_operation(opcode) if (opcode & 0x24) == 0x20
18
+ end
19
+ end
20
+
21
+ def extended_operation(opcode)
22
+ row = (opcode >> 3) & 7
23
+ case opcode & 7
24
+ when 0 then input_register(row)
25
+ when 1 then output_register(row)
26
+ when 2 then add_pair_with_carry(row)
27
+ when 3 then row.nobits?(1) ? store_word(pair(row >> 1)) : set_pair(row >> 1, load_word)
28
+ when 4 then neg
29
+ when 5 then return_from_interrupt
30
+ when 6 then @im = INTERRUPT_MODES[row]
31
+ else special_register(row)
32
+ end
33
+ end
34
+
35
+ # IN r,(C), and IN F,(C) at index 6, which sets the flags alone.
36
+ def input_register(index)
37
+ port = bc
38
+ value = input(port)
39
+ @wz = (port + 1) & 0xffff
40
+ assign_flags((@f & CF) | SZ53P[value])
41
+ set_register(index, value) unless index == 6
42
+ end
43
+
44
+ # OUT (C),r, and at index 6 OUT (C),0.
45
+ def output_register(index)
46
+ port = bc
47
+ output(port, index == 6 ? 0 : register(index))
48
+ @wz = (port + 1) & 0xffff
49
+ end
50
+
51
+ def add_pair_with_carry(row)
52
+ row.nobits?(1) ? sbc16(pair(row >> 1)) : adc16(pair(row >> 1))
53
+ @cycles += 7
54
+ end
55
+
56
+ # RETN and RETI both restore IFF1 from IFF2.
57
+ def return_from_interrupt
58
+ @iff1 = @iff2
59
+ return_from_call
60
+ end
61
+
62
+ # LD I,A, LD R,A, LD A,I, LD A,R, RRD and RLD, and two that do
63
+ # nothing.
64
+ def special_register(row)
65
+ case row
66
+ when 0 then @i = load_ir(@a)
67
+ when 1 then @r = load_ir(@a)
68
+ when 2 then @a = load_a_from_ir(@i)
69
+ when 3 then @a = load_a_from_ir(@r)
70
+ when 4 then rotate_digits(false)
71
+ when 5 then rotate_digits(true)
72
+ end
73
+ end
74
+
75
+ def load_ir(value)
76
+ @cycles += 1
77
+ value
78
+ end
79
+
80
+ # P/V copies IFF2, and an interrupt taken next clears it again.
81
+ def load_a_from_ir(value)
82
+ @cycles += 1
83
+ assign_flags((@f & CF) | SZ53[value] | (@iff2 ? PF : 0))
84
+ @after_ld_a_ir = true
85
+ value
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ CF = 0x01
6
+ NF = 0x02
7
+ PF = 0x04
8
+ XF = 0x08
9
+ HF = 0x10
10
+ YF = 0x20
11
+ ZF = 0x40
12
+ SF = 0x80
13
+
14
+ # Bits 5 and 3, which most instructions copy from a result.
15
+ XYF = YF | XF
16
+
17
+ # S, Z, Y and X as a result byte sets them.
18
+ SZ53 = Array.new(256) { |v| (v & (SF | XYF)) | (v.zero? ? ZF : 0) }.freeze
19
+
20
+ # P/V as parity: set when a byte has an even number of bits set.
21
+ PARITY = Array.new(256) { |v| (0x6996 >> ((v ^ (v >> 4)) & 0x0f)).nobits?(1) ? PF : 0 }.freeze
22
+
23
+ SZ53P = Array.new(256) { |v| SZ53[v] | PARITY[v] }.freeze
24
+
25
+ # The flags, which the core keeps in F as the chip does. Every
26
+ # instruction that sets them through the ALU also latches them in Q,
27
+ # which SCF and CCF read on the next instruction.
28
+ module Flags
29
+ private
30
+
31
+ def assign_flags(value)
32
+ @f = value
33
+ @q = value
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ # Taking NMI and INT between instructions. Either wakes a halted CPU,
6
+ # whose program counter already points past the HALT.
7
+ module InterruptAcceptance
8
+ private
9
+
10
+ # An opcode fetch whose byte is ignored, a T-state more, and a call
11
+ # to $0066. IFF2 keeps what IFF1 held, for RETN to restore.
12
+ def take_nmi
13
+ @nmi_pending = false
14
+ @halted = false
15
+ @iff1 = false
16
+ @bus.fetch(@pc)
17
+ refresh
18
+ @cycles += 5
19
+ push(@pc)
20
+ @pc = 0x66
21
+ @wz = 0x66
22
+ end
23
+
24
+ # The acknowledge cycle, an M1 with two wait states, reads a byte
25
+ # from the device. Mode 0 runs it as an opcode, which is a RST in
26
+ # practice, mode 1 calls $0038, and mode 2 calls through the vector
27
+ # at I and that byte.
28
+ def take_int
29
+ @halted = false
30
+ @f &= ~PF if @after_ld_a_ir
31
+ @iff1 = @iff2 = false
32
+ @after_ld_a_ir = false
33
+ @prefix = 0
34
+ data = @bus.acknowledge
35
+ refresh
36
+ @cycles += 6
37
+ return execute(data) if @im.zero?
38
+
39
+ @cycles += 1
40
+ push(@pc)
41
+ @pc = @im == 1 ? 0x38 : read_word((@i << 8) | data)
42
+ @wz = @pc
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ # Jumps, calls and returns. Each leaves its target in WZ, and JP cc and
6
+ # CALL cc do whether they jump or not.
7
+ module Jumps
8
+ private
9
+
10
+ # The conditions bits 5-3 pick: NZ Z NC C PO PE P M.
11
+ def condition?(index)
12
+ case index
13
+ when 0 then @f.nobits?(ZF)
14
+ when 1 then @f.anybits?(ZF)
15
+ when 2 then @f.nobits?(CF)
16
+ when 3 then @f.anybits?(CF)
17
+ when 4 then @f.nobits?(PF)
18
+ when 5 then @f.anybits?(PF)
19
+ when 6 then @f.nobits?(SF)
20
+ else @f.anybits?(SF)
21
+ end
22
+ end
23
+
24
+ # NOP, EX AF,AF', DJNZ, JR and JR cc.
25
+ def relative_jump(row)
26
+ case row
27
+ when 0 then nil
28
+ when 1 then exchange_af
29
+ when 2 then decrement_and_jump
30
+ when 3 then jump_relative(fetch_byte)
31
+ else
32
+ offset = fetch_byte
33
+ jump_relative(offset) if condition?(row - 4)
34
+ end
35
+ end
36
+
37
+ def decrement_and_jump
38
+ @cycles += 1
39
+ offset = fetch_byte
40
+ @b = (@b - 1) & 0xff
41
+ jump_relative(offset) unless @b.zero?
42
+ end
43
+
44
+ def jump_relative(offset)
45
+ @cycles += 5
46
+ @pc = (@pc + offset - (offset > 127 ? 256 : 0)) & 0xffff
47
+ @wz = @pc
48
+ end
49
+
50
+ def jump(taken)
51
+ @wz = fetch_word
52
+ @pc = @wz if taken
53
+ end
54
+
55
+ def call(taken)
56
+ @wz = fetch_word
57
+ return unless taken
58
+
59
+ @cycles += 1
60
+ push(@pc)
61
+ @pc = @wz
62
+ end
63
+
64
+ def conditional_return(index)
65
+ @cycles += 1
66
+ return_from_call if condition?(index)
67
+ end
68
+
69
+ def return_from_call
70
+ @pc = pop
71
+ @wz = @pc
72
+ end
73
+
74
+ def restart(address)
75
+ @cycles += 1
76
+ push(@pc)
77
+ @pc = address
78
+ @wz = address
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ # Loads, PUSH, and IN and OUT through a port A and n address. The loads
6
+ # through a pointer leave the address after it in WZ, or for a store of
7
+ # A, A and the low byte of that address.
8
+ module Loads
9
+ private
10
+
11
+ # LD r,r' with (HL) as index 6.
12
+ def load_register(target, source)
13
+ if source == 6
14
+ set_plain_register(target, read_byte(operand_address))
15
+ elsif target == 6
16
+ write_byte(operand_address, plain_register(source))
17
+ else
18
+ set_register(target, register(source))
19
+ end
20
+ end
21
+
22
+ # LD r,n, and LD (IX+d),n, which reads n while it adds d.
23
+ def load_immediate(index)
24
+ return set_register(index, fetch_byte) unless index == 6
25
+ return write_byte(hl, fetch_byte) if @prefix.zero?
26
+
27
+ displaced
28
+ value = fetch_byte
29
+ @cycles += 2
30
+ write_byte(@wz, value)
31
+ end
32
+
33
+ def indirect_load(row)
34
+ case row
35
+ when 0 then store_a(bc)
36
+ when 1 then load_a(bc)
37
+ when 2 then store_a(de)
38
+ when 3 then load_a(de)
39
+ when 4 then store_word(hl_or_index)
40
+ when 5 then self.hl_or_index = load_word
41
+ when 6 then store_a(fetch_word)
42
+ else load_a(fetch_word)
43
+ end
44
+ end
45
+
46
+ def load_a(address)
47
+ @a = read_byte(address)
48
+ @wz = (address + 1) & 0xffff
49
+ end
50
+
51
+ def store_a(address)
52
+ write_byte(address, @a)
53
+ @wz = (@a << 8) | ((address + 1) & 0xff)
54
+ end
55
+
56
+ def store_word(value)
57
+ address = fetch_word
58
+ write_word(address, value)
59
+ @wz = (address + 1) & 0xffff
60
+ end
61
+
62
+ def load_word
63
+ address = fetch_word
64
+ @wz = (address + 1) & 0xffff
65
+ read_word(address)
66
+ end
67
+
68
+ def push_pair(index)
69
+ @cycles += 1
70
+ push(stack_pair(index))
71
+ end
72
+
73
+ # RET, EXX, JP (HL) and LD SP,HL.
74
+ def stack_and_exchange(index)
75
+ case index
76
+ when 0 then return_from_call
77
+ when 1 then exchange_alternates
78
+ when 2 then @pc = hl_or_index
79
+ else
80
+ @cycles += 2
81
+ @sp = hl_or_index
82
+ end
83
+ end
84
+
85
+ def output_immediate
86
+ port = (@a << 8) | fetch_byte
87
+ output(port, @a)
88
+ @wz = (port & 0xff00) | ((port + 1) & 0xff)
89
+ end
90
+
91
+ def input_immediate
92
+ port = (@a << 8) | fetch_byte
93
+ @wz = (port + 1) & 0xffff
94
+ @a = input(port)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Z80
5
+ # The unprefixed opcodes, which DD and FD reuse with IX or IY for HL.
6
+ # An opcode splits into its quarter (bits 7-6), its row (5-3) and its
7
+ # column (2-0), and each quarter of the page is regular in rows and
8
+ # columns.
9
+ module MainPage
10
+ private
11
+
12
+ def execute(opcode)
13
+ case opcode >> 6
14
+ when 0 then execute_low(opcode)
15
+ when 1 then opcode == 0x76 ? @halted = true : load_register((opcode >> 3) & 7, opcode & 7)
16
+ when 2 then alu((opcode >> 3) & 7, operand(opcode & 7))
17
+ else execute_high(opcode)
18
+ end
19
+ end
20
+
21
+ def execute_low(opcode)
22
+ row = (opcode >> 3) & 7
23
+ case opcode & 7
24
+ when 0 then relative_jump(row)
25
+ when 1 then opcode.nobits?(8) ? set_pair(row >> 1, fetch_word) : add_pair(row >> 1)
26
+ when 2 then indirect_load(row)
27
+ when 3 then step_pair(row)
28
+ when 4 then modify(row, true)
29
+ when 5 then modify(row, false)
30
+ when 6 then load_immediate(row)
31
+ else accumulator_operation(row)
32
+ end
33
+ end
34
+
35
+ def execute_high(opcode)
36
+ row = (opcode >> 3) & 7
37
+ case opcode & 7
38
+ when 0 then conditional_return(row)
39
+ when 1 then opcode.nobits?(8) ? set_stack_pair(row >> 1, pop) : stack_and_exchange(row >> 1)
40
+ when 2 then jump(condition?(row))
41
+ when 3 then miscellaneous(row)
42
+ when 4 then call(condition?(row))
43
+ when 5 then opcode.nobits?(8) ? push_pair(row >> 1) : call_or_prefix(row >> 1)
44
+ when 6 then alu(row, fetch_byte)
45
+ else restart(row << 3)
46
+ end
47
+ end
48
+
49
+ # The operand of the 8-bit ALU opcodes: a register or (HL).
50
+ def operand(index)
51
+ index == 6 ? read_byte(operand_address) : register(index)
52
+ end
53
+
54
+ # INC and DEC on a register or (HL).
55
+ def modify(index, increase)
56
+ if index == 6
57
+ address = operand_address
58
+ value = read_byte(address)
59
+ @cycles += 1
60
+ write_byte(address, increase ? increment(value) : decrement(value))
61
+ else
62
+ set_register(index, increase ? increment(register(index)) : decrement(register(index)))
63
+ end
64
+ end
65
+
66
+ # INC and DEC on a pair, which touch no flags.
67
+ def step_pair(row)
68
+ @cycles += 2
69
+ index = row >> 1
70
+ set_pair(index, (pair(index) + (row.nobits?(1) ? 1 : -1)) & 0xffff)
71
+ end
72
+
73
+ def add_pair(index)
74
+ self.hl_or_index = add16(hl_or_index, pair(index))
75
+ @cycles += 7
76
+ end
77
+
78
+ def accumulator_operation(row)
79
+ case row
80
+ when 0, 1, 2, 3 then rotate_a(row)
81
+ when 4 then daa
82
+ when 5 then complement
83
+ when 6 then assign_flags((@f & (SF | ZF | PF)) | CF | (((@last_q ^ @f) | @a) & XYF))
84
+ else assign_flags((@f & (SF | ZF | PF)) | ((@f & CF) << 4) | ((@f & CF) ^ CF) | (((@last_q ^ @f) | @a) & XYF))
85
+ end
86
+ end
87
+
88
+ def complement
89
+ @a ^= 0xff
90
+ assign_flags((@f & (SF | ZF | PF | CF)) | HF | NF | (@a & XYF))
91
+ end
92
+
93
+ def miscellaneous(row)
94
+ case row
95
+ when 0 then jump(true)
96
+ when 1 then @prefix.zero? ? execute_bits(fetch_opcode) : execute_index_bits
97
+ when 2 then output_immediate
98
+ when 3 then input_immediate
99
+ when 4 then exchange_stack_top
100
+ when 5 then exchange_de_hl
101
+ when 6 then @iff1 = @iff2 = false
102
+ else enable_interrupts
103
+ end
104
+ end
105
+
106
+ def call_or_prefix(index)
107
+ case index
108
+ when 0 then call(true)
109
+ when 1 then index_prefix(1)
110
+ when 2 then extended_prefix
111
+ else index_prefix(2)
112
+ end
113
+ end
114
+
115
+ # DD and FD: the next opcode runs with IX or IY for HL. A prefix
116
+ # after a prefix replaces it.
117
+ def index_prefix(prefix)
118
+ @prefix = prefix
119
+ @last_q = 0
120
+ execute(fetch_opcode)
121
+ end
122
+
123
+ def extended_prefix
124
+ @prefix = 0
125
+ execute_extended(fetch_opcode)
126
+ end
127
+
128
+ def enable_interrupts
129
+ @iff1 = @iff2 = true
130
+ @after_ei = true
131
+ end
132
+ end
133
+ end
134
+ end